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,88 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user