32c7336bf6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
209 lines
7.4 KiB
Python
209 lines
7.4 KiB
Python
"""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()
|