104 lines
4.3 KiB
Python
104 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hmac
|
|
import json
|
|
import os
|
|
import sys
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
CONNECTOR = ROOT / "plugins" / "1c" / "connector"
|
|
if str(CONNECTOR) not in sys.path:
|
|
sys.path.insert(0, str(CONNECTOR))
|
|
|
|
import repository_control # noqa: E402
|
|
|
|
|
|
def bearer_token() -> str:
|
|
env_name = os.environ.get("ONEC_REPOSITORY_RUNNER_TOKEN_ENV", "ONEC_REPOSITORY_RUNNER_TOKEN")
|
|
return os.environ.get(env_name, "")
|
|
|
|
|
|
def authorized(header: str) -> bool:
|
|
expected = bearer_token()
|
|
if not expected:
|
|
return os.environ.get("ONEC_REPOSITORY_RUNNER_ALLOW_UNAUTHENTICATED", "").strip().casefold() == "true"
|
|
prefix = "Bearer "
|
|
return header.startswith(prefix) and hmac.compare_digest(header[len(prefix):], expected)
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
server_version = "OneCRepositoryRunner/1"
|
|
|
|
def _json(self, status: int, value: dict[str, Any]) -> None:
|
|
body = json.dumps(value, ensure_ascii=False).encode("utf-8")
|
|
self.send_response(status)
|
|
self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def do_GET(self) -> None: # noqa: N802
|
|
if self.path == "/healthz":
|
|
self._json(200, {"status": "ok", "service": "onec-repository-runner", "auth_configured": bool(bearer_token())})
|
|
return
|
|
self._json(404, {"status": "not_found"})
|
|
|
|
def do_POST(self) -> None: # noqa: N802
|
|
if self.path != "/repository/execute":
|
|
self._json(404, {"status": "not_found"})
|
|
return
|
|
if not authorized(self.headers.get("Authorization", "")):
|
|
self._json(401, {"status": "unauthorized"})
|
|
return
|
|
try:
|
|
length = int(self.headers.get("Content-Length", "0"))
|
|
if length <= 0 or length > 1024 * 1024:
|
|
raise ValueError("invalid request size")
|
|
payload = json.loads(self.rfile.read(length).decode("utf-8"))
|
|
if not isinstance(payload, dict):
|
|
raise ValueError("request must be an object")
|
|
except (ValueError, json.JSONDecodeError, UnicodeDecodeError) as exc:
|
|
self._json(400, {"status": "invalid_request", "message": str(exc)})
|
|
return
|
|
base_id = str(payload.get("base_id") or "").strip()
|
|
action = str(payload.get("action") or "").strip().casefold()
|
|
objects = payload.get("objects") or []
|
|
if not base_id or action not in {"report", "lock", "unlock", "commit"}:
|
|
self._json(400, {"status": "invalid_request", "message": "base_id and a supported action are required"})
|
|
return
|
|
if not isinstance(objects, list) or any(not isinstance(item, str) or not item.strip() for item in objects) or len(objects) > 10000:
|
|
self._json(400, {"status": "invalid_request", "message": "objects must be a bounded string array"})
|
|
return
|
|
config, error = repository_control.repository_config(base_id)
|
|
if error:
|
|
self._json(400, error)
|
|
return
|
|
if (config.get("runner") or {}).get("kind") != "local":
|
|
self._json(400, {"status": "invalid_config", "message": "Windows runner base configuration must use runner.kind=local"})
|
|
return
|
|
result = repository_control._execute_repository(
|
|
base_id, config, action, 180, objects=objects,
|
|
comment=str(payload.get("comment") or ""), keep_locked=payload.get("keep_locked") is True,
|
|
)
|
|
self._json(200 if result.get("status") == "ok" else 409, result)
|
|
|
|
def log_message(self, format: str, *args: Any) -> None:
|
|
return
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Authenticated Windows runner for standard 1C Designer repository commands.")
|
|
parser.add_argument("--host", default=os.environ.get("ONEC_REPOSITORY_RUNNER_HOST", "127.0.0.1"))
|
|
parser.add_argument("--port", type=int, default=int(os.environ.get("ONEC_REPOSITORY_RUNNER_PORT", "8121")))
|
|
args = parser.parse_args()
|
|
ThreadingHTTPServer((args.host, args.port), Handler).serve_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|