#!/usr/bin/env python3 from __future__ import annotations import argparse import json import subprocess from pathlib import Path from typing import Any DEFAULT_REGISTRY = ( Path(__file__).resolve().parents[1] / "app-instance" / "runtime" / "registry" / "instances.json" ) DEFAULT_OUTPUT = Path(__file__).resolve().parent / "runtime" / "conf.d" / "instances.conf" def load_instances(path: Path) -> list[dict[str, Any]]: if not path.exists(): return [] try: data = json.loads(path.read_text(encoding="utf-8")) except json.JSONDecodeError: return [] items = data.get("instances", []) return [item for item in items if isinstance(item, dict)] def is_container_running(container_name: str) -> bool: name = container_name.strip() if not name: return False try: result = subprocess.run( ["docker", "inspect", "-f", "{{.State.Running}}", name], check=False, capture_output=True, text=True, ) except OSError: return False return result.returncode == 0 and result.stdout.strip().lower() == "true" def render_server(instance_host: str, container_name: str, upstream_port: int) -> str: return f"""server {{ listen 80; server_name {instance_host}; location / {{ proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_pass http://{container_name}:{upstream_port}; }} }} """ def render(instances: list[dict[str, Any]], upstream_port: int) -> str: blocks = [ "server {\n" " listen 80 default_server;\n" " server_name _;\n" " return 404;\n" "}\n" ] seen_hosts: set[str] = set() for item in sorted(instances, key=lambda value: str(value.get("instance_host", ""))): instance_host = str(item.get("instance_host", "") or "").strip() container_name = str(item.get("container_name", "") or "").strip() if ( not instance_host or not container_name or instance_host in seen_hosts or not is_container_running(container_name) ): continue seen_hosts.add(instance_host) blocks.append(render_server(instance_host, container_name, upstream_port)) return "\n".join(blocks) def main() -> int: parser = argparse.ArgumentParser(description="Render nginx routes for app instances.") parser.add_argument("--registry", default=str(DEFAULT_REGISTRY), help="Registry JSON path.") parser.add_argument("--output", default=str(DEFAULT_OUTPUT), help="Nginx output config path.") parser.add_argument("--upstream-port", default=8080, type=int, help="App container upstream port.") args = parser.parse_args() registry_path = Path(args.registry).expanduser() output_path = Path(args.output).expanduser() output_path.parent.mkdir(parents=True, exist_ok=True) contents = render(load_instances(registry_path), args.upstream_port) output_path.write_text(contents, encoding="utf-8") print(output_path) return 0 if __name__ == "__main__": raise SystemExit(main())