101 lines
4.1 KiB
Python
Executable File
101 lines
4.1 KiB
Python
Executable File
"""Single supervised socket-activated worker for durable GitOps Jobs.
|
|
|
|
The worker consumes durable requests and reconciliation queries. Repository
|
|
mutation is a separate injected executor and remains impossible while the
|
|
legacy global write gate is false.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import signal
|
|
import socket
|
|
import threading
|
|
from typing import Any, Mapping
|
|
|
|
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore, ServiceUnavailable
|
|
|
|
|
|
def runtime_policy(environment: Mapping[str, str] | None = None) -> dict[str, bool]:
|
|
env = environment or os.environ
|
|
legacy = env.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
|
|
durable = env.get("AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
|
|
# Legacy direct Git mutation remains disabled. Authorized durable Jobs use
|
|
# their own campaign/task capability and may mutate only through the
|
|
# worker's lease/CAS path while the legacy gate stays false.
|
|
return {"legacy_global_write": legacy, "durable_job_execution": durable, "repository_mutation_allowed": durable}
|
|
|
|
|
|
def handle_request(controller: GitOpsJobController, request: Mapping[str, Any], executor: Any | None = None) -> dict[str, Any]:
|
|
job_id = str(request.get("job_id") or "")
|
|
if not job_id:
|
|
raise JobError("JOB_ID_REQUIRED")
|
|
if request.get("action") == "execute":
|
|
if executor is None:
|
|
raise JobError("JOB_EXECUTOR_UNAVAILABLE")
|
|
result = executor.execute(job_id, dry_run=bool(request.get("dry_run", False)))
|
|
receipt = controller.store._receipt(controller.store.read(job_id), duplicate=True)
|
|
return {"job": result, **receipt.as_dict()}
|
|
if request.get("action") == "resume":
|
|
receipt = controller.resume(job_id)
|
|
else:
|
|
row = controller.store.read(job_id)
|
|
receipt = controller.store._receipt(row, duplicate=True)
|
|
return receipt.as_dict()
|
|
|
|
|
|
def serve_socket(sock: socket.socket, controller: GitOpsJobController, stopping: threading.Event, executor: Any | None = None) -> None:
|
|
sock.listen(32)
|
|
sock.settimeout(1.0)
|
|
while not stopping.is_set():
|
|
try:
|
|
connection, _ = sock.accept()
|
|
except socket.timeout:
|
|
continue
|
|
with connection:
|
|
try:
|
|
raw = connection.makefile("rb").readline(1_048_576)
|
|
request = json.loads(raw.decode("utf-8"))
|
|
response = {"status": "ok", **handle_request(controller, request, executor)}
|
|
except ServiceUnavailable as exc:
|
|
response = {"status": "error", "error": str(exc), "retryable": True, "side_effect_state": "UNKNOWN"}
|
|
except Exception as exc:
|
|
response = {"status": "error", "error": type(exc).__name__, "retryable": False}
|
|
connection.sendall((json.dumps(response, sort_keys=True) + "\n").encode("utf-8"))
|
|
|
|
|
|
def socket_from_systemd() -> socket.socket:
|
|
if os.environ.get("LISTEN_FDS") != "1":
|
|
raise RuntimeError("SOCKET_ACTIVATION_REQUIRED")
|
|
return socket.fromfd(3, socket.AF_UNIX, socket.SOCK_STREAM)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--job-root", required=True, type=Path)
|
|
args = parser.parse_args(argv)
|
|
policy = runtime_policy()
|
|
if policy["legacy_global_write"]:
|
|
raise RuntimeError("LEGACY_GLOBAL_WRITES_MUST_REMAIN_FALSE")
|
|
if not policy["durable_job_execution"]:
|
|
raise RuntimeError("DURABLE_JOB_EXECUTION_GATE_CLOSED")
|
|
controller = GitOpsJobController(JobStore(args.job_root))
|
|
from aios_gitops_job_executor import build_from_environment
|
|
executor = build_from_environment(args.job_root)
|
|
stopping = threading.Event()
|
|
signal.signal(signal.SIGTERM, lambda *_: stopping.set())
|
|
signal.signal(signal.SIGINT, lambda *_: stopping.set())
|
|
sock = socket_from_systemd()
|
|
try:
|
|
serve_socket(sock, controller, stopping, executor)
|
|
finally:
|
|
sock.close()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|