feat(infra): auto-commit con 56 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
"""refresh_local_hub — orquesta el refresco del sistema local_hub.
|
||||
|
||||
Pipeline impuro del dominio `pipelines`. Compone tres funciones del registry para
|
||||
regenerar la infraestructura que expone los servicios locales como subdominios
|
||||
`*.localhost`:
|
||||
|
||||
1. discover_local_services_py_infra — descubre y normaliza los servicios (manifiesto
|
||||
+ servicios del registry con bloque `service:`), comprobando estado up/down.
|
||||
2. render_caddyfile_py_infra — genera el fragmento de Caddyfile.
|
||||
3. render_glance_config_py_infra — genera la config del widget monitor de Glance.
|
||||
|
||||
Después escribe el fragmento de Caddyfile en /etc/caddy/conf.d/local_hub.caddy (el
|
||||
usuario tiene ACL de escritura ahí, sin sudo) y la config de Glance en
|
||||
apps/local_hub/glance/glance.yml. Si `reload=True`, recarga Caddy (admin API en
|
||||
localhost:2019, sin sudo) y reinicia la user-unit `glance` (sin sudo).
|
||||
|
||||
Pensado para correrse a diario desde dag_engine con un step `function:`, o a mano:
|
||||
`fn run refresh_local_hub`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
||||
sys.path.insert(0, os.path.join(ROOT, "python", "functions"))
|
||||
|
||||
from infra.discover_local_services import discover_local_services # noqa: E402
|
||||
from infra.render_caddyfile import render_caddyfile # noqa: E402
|
||||
from infra.render_glance_config import render_glance_config # noqa: E402
|
||||
|
||||
# Ruta del fragmento de Caddyfile. El usuario tiene ACL de escritura aquí (sin sudo).
|
||||
CADDY_FRAGMENT_PATH = "/etc/caddy/conf.d/local_hub.caddy"
|
||||
|
||||
# Bloque fijo de servidor + tema que precede a la salida de render_glance_config.
|
||||
GLANCE_SERVER_BLOCK = (
|
||||
"server:\n"
|
||||
" host: 127.0.0.1\n"
|
||||
" port: 8585\n"
|
||||
"\n"
|
||||
"theme:\n"
|
||||
" background-color: 240 8 9\n"
|
||||
" contrast-multiplier: 1.2\n"
|
||||
" primary-color: 210 90 70\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
|
||||
def _default_manifest_path() -> str:
|
||||
"""Ruta por defecto del manifiesto, derivada de la raíz del registry."""
|
||||
root = os.environ.get("FN_REGISTRY_ROOT") or ROOT
|
||||
return os.path.join(root, "apps", "local_hub", "local_services.yaml")
|
||||
|
||||
|
||||
def _registry_root() -> str:
|
||||
"""Raíz del registry: FN_REGISTRY_ROOT si está, si no la derivada del path del módulo."""
|
||||
return os.environ.get("FN_REGISTRY_ROOT") or ROOT
|
||||
|
||||
|
||||
def refresh_local_hub(
|
||||
manifest_path: str | None = None,
|
||||
reload: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""Refresca el sistema local_hub: descubre servicios, regenera configs y recarga.
|
||||
|
||||
Args:
|
||||
manifest_path: ruta al manifiesto YAML del local_hub. Si es None, se usa
|
||||
``<RAIZ>/apps/local_hub/local_services.yaml`` (RAIZ derivada de
|
||||
FN_REGISTRY_ROOT o del path del propio módulo).
|
||||
reload: si True, recarga Caddy (admin API en localhost:2019) y reinicia la
|
||||
user-unit ``glance``. Si False, solo escribe las configs y no toca
|
||||
ningún servicio.
|
||||
|
||||
Returns:
|
||||
dict resumen con las claves: total, up, down, caddy_path, glance_path,
|
||||
reloaded, caddy_reload_rc, glance_restart_rc, services.
|
||||
|
||||
Raises:
|
||||
RuntimeError: si la config de Glance generada no es YAML parseable.
|
||||
"""
|
||||
if manifest_path is None:
|
||||
manifest_path = _default_manifest_path()
|
||||
|
||||
# 1. Lee el manifiesto para extraer dashboard_subdomain y glance_port.
|
||||
try:
|
||||
with open(manifest_path, "r", encoding="utf-8") as fh:
|
||||
manifest = yaml.safe_load(fh) or {}
|
||||
except (OSError, yaml.YAMLError) as exc:
|
||||
raise RuntimeError(
|
||||
f"refresh_local_hub: no se puede leer el manifiesto {manifest_path}: {exc}"
|
||||
) from exc
|
||||
|
||||
dashboard_subdomain = manifest.get("dashboard_subdomain") or "home"
|
||||
glance_port = manifest.get("glance_port") or 8585
|
||||
|
||||
# 2. Descubre los servicios (manifiesto + registry).
|
||||
services = discover_local_services(manifest_path, include_registry=True)
|
||||
|
||||
# 3. Bloque del dashboard para el Caddyfile.
|
||||
dashboard = {"subdomain": dashboard_subdomain, "port": glance_port}
|
||||
|
||||
# 4. Renderiza el fragmento de Caddyfile.
|
||||
caddy_text = render_caddyfile(services, dashboard)
|
||||
|
||||
# 5. Construye la config completa de Glance (bloque fijo + render_glance_config).
|
||||
glance_full = GLANCE_SERVER_BLOCK + render_glance_config(services)
|
||||
|
||||
# Verifica que el YAML resultante es parseable antes de escribir nada.
|
||||
try:
|
||||
yaml.safe_load(glance_full)
|
||||
except yaml.YAMLError as exc:
|
||||
raise RuntimeError(
|
||||
f"refresh_local_hub: la config de Glance generada no es YAML válido: {exc}"
|
||||
) from exc
|
||||
|
||||
# 6. Escribe el fragmento de Caddyfile (ACL del usuario, sin sudo).
|
||||
with open(CADDY_FRAGMENT_PATH, "w", encoding="utf-8") as fh:
|
||||
fh.write(caddy_text)
|
||||
|
||||
# 7. Escribe la config de Glance (crea el dir si falta).
|
||||
glance_path = os.path.join(_registry_root(), "apps", "local_hub", "glance", "glance.yml")
|
||||
os.makedirs(os.path.dirname(glance_path), exist_ok=True)
|
||||
with open(glance_path, "w", encoding="utf-8") as fh:
|
||||
fh.write(glance_full)
|
||||
|
||||
up = sum(1 for s in services if s.get("up"))
|
||||
down = len(services) - up
|
||||
|
||||
caddy_reload_rc: int | None = None
|
||||
glance_restart_rc: int | None = None
|
||||
|
||||
# 8. Recarga Caddy y reinicia Glance (ambos sin sudo).
|
||||
if reload:
|
||||
try:
|
||||
caddy_proc = subprocess.run(
|
||||
["caddy", "reload", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
caddy_reload_rc = caddy_proc.returncode
|
||||
except (OSError, subprocess.SubprocessError) as exc:
|
||||
caddy_reload_rc = -1
|
||||
sys.stderr.write(f"refresh_local_hub: fallo recargando Caddy: {exc}\n")
|
||||
|
||||
try:
|
||||
glance_proc = subprocess.run(
|
||||
["systemctl", "--user", "restart", "glance"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
glance_restart_rc = glance_proc.returncode
|
||||
except (OSError, subprocess.SubprocessError) as exc:
|
||||
glance_restart_rc = -1
|
||||
sys.stderr.write(f"refresh_local_hub: fallo reiniciando Glance: {exc}\n")
|
||||
|
||||
return {
|
||||
"total": len(services),
|
||||
"up": up,
|
||||
"down": down,
|
||||
"caddy_path": CADDY_FRAGMENT_PATH,
|
||||
"glance_path": glance_path,
|
||||
"reloaded": reload,
|
||||
"caddy_reload_rc": caddy_reload_rc,
|
||||
"glance_restart_rc": glance_restart_rc,
|
||||
"services": [
|
||||
{
|
||||
"name": s.get("name"),
|
||||
"subdomain": s.get("subdomain"),
|
||||
"port": s.get("port"),
|
||||
"up": s.get("up"),
|
||||
}
|
||||
for s in services
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Refresca el sistema local_hub.")
|
||||
parser.add_argument(
|
||||
"--manifest-path",
|
||||
default=None,
|
||||
help="Ruta al manifiesto YAML del local_hub (default: <RAIZ>/apps/local_hub/local_services.yaml).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-reload",
|
||||
action="store_true",
|
||||
help="No recargar Caddy ni reiniciar Glance; solo regenerar las configs.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
result = refresh_local_hub(
|
||||
manifest_path=args.manifest_path,
|
||||
reload=not args.no_reload,
|
||||
)
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user