Files
fn_registry/python/functions/infra/render_caddyfile.py
T
egutierrez 32c7336bf6 feat(infra): auto-commit con 56 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-21 14:22:55 +02:00

89 lines
3.6 KiB
Python

"""Renderiza un fragmento de Caddyfile para el sistema local_hub.
Funcion pura: transforma una lista de servicios normalizados en el texto de un
Caddyfile que mapea cada subdominio `*.localhost` a su puerto local via
reverse_proxy HTTP plano (loopback, sin TLS). Sin I/O, sin red, determinista.
"""
_HEADER = (
"# Generado por render_caddyfile_py_infra — NO editar a mano. "
"Fuente: apps/local_hub/local_services.yaml\n"
)
def _render_block(subdomain: str, port: int, rewrite_host: bool = False) -> str:
"""Construye un bloque reverse_proxy para un subdominio y puerto dados.
Args:
subdomain: subdominio sin el sufijo `.localhost` (ej. "metabase").
port: puerto local al que redirigir (ej. 3030).
rewrite_host: si es True, reescribe la cabecera `Host` enviada al
upstream a `127.0.0.1:<port>`. Necesario para servicios que validan
el header Host y rechazan el subdominio (ej. Jupyter devuelve 400
"Bad Request" si recibe `Host: jupyter.localhost`).
Returns:
el bloque Caddyfile como string, con indentacion de 4 espacios y
terminado en `\n`.
"""
if rewrite_host:
return (
f"http://{subdomain}.localhost {{\n"
f" reverse_proxy 127.0.0.1:{port} {{\n"
f" header_up Host 127.0.0.1:{port}\n"
f" }}\n"
f"}}\n"
)
return (
f"http://{subdomain}.localhost {{\n"
f" reverse_proxy 127.0.0.1:{port}\n"
f"}}\n"
)
def render_caddyfile(services: list[dict], dashboard: dict | None = None) -> str:
"""Renderiza el texto de un fragmento de Caddyfile para local_hub.
Cada servicio se mapea a un bloque `http://<subdomain>.localhost` con un
`reverse_proxy 127.0.0.1:<port>`. Los bloques de servicio se ordenan por
subdominio alfabetico para que la salida sea estable y reproducible. El
bloque del dashboard, si se pasa, va siempre primero (es la pagina
principal). Se usa HTTP plano a proposito: todo es loopback, no hay TLS.
Args:
services: lista de dicts de servicio. Cada uno debe tener al menos
`subdomain` (str) y `port` (int); otras claves se ignoran salvo
`rewrite_host` (bool opcional): si es truthy, el bloque reescribe la
cabecera `Host` enviada al upstream a `127.0.0.1:<port>` (para
servicios que validan Host, ej. Jupyter). Los servicios sin
`subdomain` o sin `port` se saltan (no lanzan error). No se
deduplica (eso es trabajo del discover).
dashboard: dict opcional con `subdomain` y `port` para la pagina
principal. Si es None, no se emite bloque de dashboard. Si le falta
`subdomain` o `port`, se ignora igual que un servicio invalido.
Returns:
el Caddyfile completo como string: empieza por una cabecera de
comentario, luego (si aplica) el bloque del dashboard, y despues los
bloques de servicio ordenados. Termina con un unico `\n`.
"""
parts: list[str] = [_HEADER]
if dashboard is not None:
d_sub = dashboard.get("subdomain")
d_port = dashboard.get("port")
if d_sub is not None and d_port is not None:
parts.append(_render_block(d_sub, d_port))
valid = [
svc
for svc in services
if svc.get("subdomain") is not None and svc.get("port") is not None
]
for svc in sorted(valid, key=lambda s: s["subdomain"]):
parts.append(
_render_block(svc["subdomain"], svc["port"], bool(svc.get("rewrite_host")))
)
return "".join(parts)