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