c7f2ea128e
- deploy/api_stub.py: make_server() выделен из main(), main(argv) — тесты поднимают настоящий сервер на порту 0 (покрытие api_stub.py 0 % -> 98 %). - Makefile: make test-deploy (гейт ≥70 % через tests/deploy/pytest.ini), test-netns-ci прокидывает AWG_NS_EXEC_MODE. Refs #21, Refs #15, Refs #56
96 lines
3.7 KiB
Python
96 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
||
"""api_stub.py — DEV-ONLY заглушка health-эндпоинта (задача #21).
|
||
|
||
Существует ровно для одной цели: до реализации FastAPI-каркаса (#31) проверить
|
||
плумбинг dev-стенда — маппинг порта, healthcheck контейнера, тома /config и
|
||
/data, переживание `docker compose restart`.
|
||
|
||
Отдаёт:
|
||
GET /api/v1/health -> 200 {"status":"ok","stub":true,...}
|
||
GET /healthz -> 200 (алиас для отладки)
|
||
всё остальное -> 404 {"error":"not_implemented","issue":"#31"}
|
||
|
||
Чего НЕ делает: никакой логики шлюза, никакого доступа к awg/iptables.
|
||
В staging/prod запускать запрещено (см. deploy/dev-stand.md).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import time
|
||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||
|
||
STARTED_AT = time.time()
|
||
STUB_ENV = os.environ.get("AWG_API_STUB", "0")
|
||
|
||
HEALTH_PATHS = ("/api/v1/health", "/healthz")
|
||
|
||
|
||
class Handler(BaseHTTPRequestHandler):
|
||
server_version = "awg-dash-stub/0.1"
|
||
protocol_version = "HTTP/1.1"
|
||
|
||
def _json(self, code: int, payload: dict) -> None:
|
||
body = (json.dumps(payload, sort_keys=True) + "\n").encode()
|
||
self.send_response(code)
|
||
self.send_header("Content-Type", "application/json")
|
||
self.send_header("Content-Length", str(len(body)))
|
||
self.end_headers()
|
||
self.wfile.write(body)
|
||
|
||
def do_GET(self) -> None: # noqa: N802 (HTTP-метод)
|
||
path = self.path.split("?", 1)[0]
|
||
if path in HEALTH_PATHS:
|
||
self._json(
|
||
200,
|
||
{
|
||
"status": "ok",
|
||
"stub": True,
|
||
"issue": "#31",
|
||
"note": "заглушка dev-стенда: реальный API появится в #31",
|
||
"uptime_s": round(time.time() - STARTED_AT, 3),
|
||
"stub_env": STUB_ENV,
|
||
},
|
||
)
|
||
return
|
||
self._json(404, {"error": "not_implemented", "issue": "#31", "path": path})
|
||
|
||
def do_HEAD(self) -> None: # noqa: N802
|
||
self.send_response(200 if self.path.split("?", 1)[0] in HEALTH_PATHS else 404)
|
||
self.send_header("Content-Length", "0")
|
||
self.end_headers()
|
||
|
||
def log_message(self, fmt: str, *args) -> None: # лог в stdout контейнера
|
||
print(f"[api-stub] {self.address_string()} {fmt % args}", flush=True)
|
||
|
||
|
||
def make_server(host: str, port: int) -> ThreadingHTTPServer:
|
||
"""HTTP-сервер заглушки.
|
||
|
||
Вынесен из main(), чтобы тесты (tests/deploy/test_api_stub.py) поднимали
|
||
настоящий HTTP-сервер на свободном порту (port=0) и проверяли коды ответов.
|
||
"""
|
||
return ThreadingHTTPServer((host, port), Handler)
|
||
|
||
|
||
def main(argv: list[str] | None = None) -> int:
|
||
ap = argparse.ArgumentParser(description="DEV-ONLY заглушка health для dev-стенда (#21)")
|
||
ap.add_argument("--host", default="0.0.0.0")
|
||
ap.add_argument("--port", type=int, default=8080)
|
||
ap.add_argument("--domain", default="/config", help="каталог, доступность которого проверяем (для лога)")
|
||
args = ap.parse_args(argv)
|
||
|
||
print(
|
||
f"[api-stub] DEV-ONLY заглушка на {args.host}:{args.port}; каталог {args.domain} "
|
||
f"{'доступен' if os.path.isdir(args.domain) else 'НЕ доступен'}",
|
||
flush=True,
|
||
)
|
||
make_server(args.host, args.port).serve_forever()
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|