Traducir de a pares no escala: con nginx, apache, caddy, haproxy y lighttpd son 20 traductores, y cada formato nuevo agrega 2N. Con un modelo intermedio son N lectores + M escritores: un formato nuevo cuesta uno o dos plugins y estrena todos los pares de golpe. **Probado, no argumentado**: el lector de apache se agregó SIN TOCAR el escritor de Caddy, y con eso `apache → caddy` funciona sin que exista ningún traductor de ese par. Las dos salidas las valida el caddy del propio corpus: `Valid configuration` en las dos. Los plugins se DESCUBREN (`formatos/` + `@lector` / `@escritor`): una lista mantenida a mano se desincroniza y el formato nuevo «no existe» sin que nada falle. `--list` dice qué pares habilita. **El acta viaja DENTRO del modelo**, y es la decisión que hace que el pivote no mienta: si fuera un efecto de cada traductor, lo que el LECTOR no entendió se perdería antes de llegar al escritor. El escritor además puede agregarle — Caddy no tiene equivalente de `location ~`, así que en vez de emitir un `handle` de prefijo que SE PARECE, lo manda al acta. Parecerse es peor que faltar. Y un bug del lector de apache, encontrado probando: `ProxyPass` tiene dos formas y dentro de `<Location>` lleva sólo la URL. Mirar sólo la de tres tokens dejaba sin traducir la forma más común y el acta la reportaba como «no reconocida» — un falso negativo que manda a escribir a mano algo que el lector sí sabe hacer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
75 lines
3.3 KiB
Python
75 lines
3.3 KiB
Python
"""Plugin de formato: ESCRITOR de Caddy (SDD 29)."""
|
|
import os, sys
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from formatos import escritor # noqa: E402
|
|
|
|
|
|
def _direcciones(s):
|
|
"""En nginx dos `server` con el MISMO nombre se distinguen por su `listen`; en Caddy, por el
|
|
ESQUEMA de la dirección. Sin traducir eso, los bloques colisionan y caddy rechaza el fichero
|
|
entero con «ambiguous site definition» — encontrado validando con el binario real, no leyendo."""
|
|
solo_80 = bool(s.puertos) and all(p == "80" for p in s.puertos)
|
|
pref = "http://" if solo_80 else ""
|
|
return ", ".join(pref + n for n in s.nombres) or (":80" if solo_80 else ":443")
|
|
|
|
|
|
def _emitir_acciones(acciones, ind):
|
|
L = []
|
|
for a in acciones:
|
|
if a.tipo == "proxy":
|
|
L.append(f"{ind}reverse_proxy {a.valor.rstrip('/').replace('http://','').replace('https://','')}")
|
|
elif a.tipo == "archivos":
|
|
L.append(f"{ind}root * {a.valor}")
|
|
elif a.tipo == "intentar":
|
|
L.append(f"{ind}try_files {a.valor}")
|
|
elif a.tipo == "redir":
|
|
p = a.valor.split()
|
|
code = p[0] if p and p[0].isdigit() else ""
|
|
dest = " ".join(p[1:]) if code else a.valor
|
|
L.append(f"{ind}redir {dest} {code}".rstrip() if dest else f"{ind}respond {code}")
|
|
return L
|
|
|
|
|
|
@escritor("caddy")
|
|
def escribir(cfg):
|
|
A = cfg.acta
|
|
L = [f"# Generado por scripts/mudanza/traducir.py desde una configuración de {cfg.formato_origen}.",
|
|
"# ⚠ NO es una traducción completa. Abajo va el ACTA de lo que NO se pudo traducir, con su",
|
|
"# línea de origen. Desplegar esto sin resolverlo es desplegar un servidor incompleto.",
|
|
""]
|
|
for s in cfg.sitios:
|
|
L.append(f"{_direcciones(s)} {{")
|
|
L.append(f"\t# desde la línea {s.origen} del {cfg.formato_origen} original")
|
|
if s.tls.get("cert"):
|
|
L.append(f"\ttls {s.tls.get('cert','')} {s.tls.get('key','')}")
|
|
if s.raiz:
|
|
L.append(f"\troot * {s.raiz}")
|
|
L += _emitir_acciones(s.acciones, "\t")
|
|
for r in s.rutas:
|
|
if r.clase == "regex":
|
|
# Caddy no tiene un equivalente directo de `location ~`. Se DECLARA en el acta en vez
|
|
# de emitir algo parecido: un `handle` de prefijo no es lo mismo que una regex, y
|
|
# parecerse es peor que faltar.
|
|
A.anota(r.origen, "SIN-TRADUCIR", f"ruta por regex: {r.patron}",
|
|
"Caddy no tiene equivalente directo de `location ~`; se escribe con "
|
|
"`@matcher path_regexp` y hay que revisar el orden de evaluación")
|
|
continue
|
|
cuerpo = _emitir_acciones(r.acciones, "\t\t")
|
|
if cuerpo:
|
|
L.append(f"\thandle {r.patron} {{ # línea {r.origen}")
|
|
L += cuerpo
|
|
L.append("\t}")
|
|
if s.raiz:
|
|
L.append("\tfile_server")
|
|
L.append("}")
|
|
L.append("")
|
|
|
|
pend = A.sin_traducir()
|
|
if pend:
|
|
L.append("# ══ ACTA: lo que NO se tradujo — hay que resolverlo a mano ══")
|
|
for e in pend:
|
|
L.append(f"# línea {e['linea']:>4} {e['directiva'][:70]}")
|
|
if e["motivo"]:
|
|
L.append(f"# └─ {e['motivo'][:96]}")
|
|
return "\n".join(L) + "\n"
|