Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user