All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 2m32s
De testsuite controleert alleen of een sjabloon geldige YAML oplevert. Een depends_on in de verkeerde vorm of network_mode naast networks is prima YAML en een kapotte stack — dat merkte je pas als iemand de app installeerde. - tools/render_voor_validatie.py rendert elk sjabloon naar echte compose-bestanden; sjablonen met schakelbare onderdelen leveren meerdere varianten op (standaard, alles aan, met en zonder VPN), zodat ook de conditionele blokken langskomen die met alleen de standaardwaarden nooit gerenderd worden - Nieuwe stap in build.yml, deploy.yml en deploy-prod.yml die er `docker compose config` overheen haalt. Renderen gebeurt in de python-container, valideren op de runner zelf: Python en Docker staan in CI niet op dezelfde plek 145 bestanden uit 142 sjablonen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q9eqpADJSRs49SoGGr4NAy
92 lines
3.7 KiB
Python
92 lines
3.7 KiB
Python
"""Render elk app-sjabloon naar echte compose-bestanden, om te laten valideren.
|
|
|
|
`yaml.safe_load` in de testsuite zegt alleen dat het geldige YAML is. Of
|
|
`docker compose` het ook accepteert is een andere vraag: een `depends_on` in de
|
|
verkeerde vorm, een poortnotatie die net niet klopt, een `network_mode` naast
|
|
`networks` — dat zijn geldige YAML én kapotte stacks. Die fout merk je anders pas
|
|
als iemand de app installeert.
|
|
|
|
Docker draait niet overal waar Python draait (in CI zitten ze in verschillende
|
|
containers), dus dit script rendert alleen. Het valideren doet
|
|
`.forgejo/workflows/*.yml` erna met `docker compose config`.
|
|
|
|
python3 tools/render_voor_validatie.py [doelmap]
|
|
|
|
Schrijft <doelmap>/<app>__<variant>/docker-compose.yml en meldt hoeveel het er
|
|
zijn. Sjablonen met schakelbare onderdelen leveren meer dan één variant op: de
|
|
standaardkeuze, alles aan en alles uit dekken de conditionele blokken af die met
|
|
alleen de standaardwaarden nooit gerenderd worden.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
WORTEL = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(WORTEL / "server-up"))
|
|
os.environ.setdefault("SU_CONFIG", "/tmp/su-render-validatie.json")
|
|
|
|
from core import boilerplates as bp # noqa: E402
|
|
|
|
|
|
def _waarden(app: Path, overrides: dict) -> dict:
|
|
velden = bp.fields(app)
|
|
uit = {f["name"]: f["default"] for f in velden if f.get("default") is not None}
|
|
uit.setdefault("service_name", app.name.replace("-", ""))
|
|
# Een verplicht veld zonder standaardwaarde zou leeg renderen; compose ziet
|
|
# dan bijvoorbeeld een lege image-naam en klaagt over iets anders.
|
|
for f in velden:
|
|
if f.get("required") and not uit.get(f["name"]):
|
|
uit[f["name"]] = "testwaarde12345678901234567890ab"
|
|
uit.update(overrides)
|
|
return uit
|
|
|
|
|
|
def varianten(app: Path) -> list[tuple[str, dict]]:
|
|
"""Welke combinaties zijn de moeite van het valideren waard?"""
|
|
schakelaars = [f["name"] for f in bp.fields(app) if f.get("is_group_toggle")]
|
|
if not schakelaars:
|
|
return [("standaard", {})]
|
|
uit = {s: False for s in schakelaars}
|
|
combinaties = [
|
|
("standaard", {}),
|
|
("alles-aan", {s: True for s in schakelaars}),
|
|
]
|
|
# Alles uit levert een bestand zonder services op; dat is geen geldige
|
|
# compose, maar wél precies wat de gebruiker krijgt als hij alles uitzet.
|
|
# Dat hoort de app af te vangen, niet docker compose — dus overslaan.
|
|
if "enable_gluetun" in schakelaars:
|
|
# De VPN verandert de netwerkopzet van elke downloadclient; dat is de
|
|
# combinatie waar het echt mis kan gaan.
|
|
combinaties.append(("vpn", {s: True for s in schakelaars}))
|
|
combinaties.append(("vpn-minimaal", uit | {"enable_gluetun": True,
|
|
"enable_qbittorrent": True}))
|
|
return combinaties
|
|
|
|
|
|
def main() -> int:
|
|
doel = Path(sys.argv[1] if len(sys.argv) > 1 else ".compose-check").resolve()
|
|
if doel.exists():
|
|
shutil.rmtree(doel)
|
|
doel.mkdir(parents=True)
|
|
|
|
apps = sorted(d for d in (WORTEL / "apps").iterdir()
|
|
if d.is_dir() and (d / "template.json").is_file())
|
|
n = 0
|
|
for app in apps:
|
|
for naam, overrides in varianten(app):
|
|
uit = doel / f"{app.name}__{naam}"
|
|
try:
|
|
bp.render_to_dir(app, uit, _waarden(app, overrides))
|
|
except Exception as e:
|
|
print(f"FOUT {app.name} ({naam}): {e}", file=sys.stderr)
|
|
return 1
|
|
n += 1
|
|
print(f"{n} compose-bestanden gerenderd uit {len(apps)} sjablonen → {doel}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|