feat(harness): allocate canonical issue-scoped broker runtime
This commit is contained in:
@@ -17,6 +17,7 @@ if __package__ in (None, ""):
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
|
||||
from harness.lib.managed_lock import LockTimeoutError, ManagedLockSet, probe_free
|
||||
from harness.lib.runtime_root import issue_runtime_path, prepare_issue_runtime
|
||||
|
||||
ROLES = {"gitea-impl-agent", "gitea-spec-agent", "gitea-review-agent"}
|
||||
MAX_TTL = 3600
|
||||
@@ -184,13 +185,18 @@ class Broker:
|
||||
raise ValueError("issue must be positive")
|
||||
if not 1 <= args.ttl_seconds <= MAX_TTL:
|
||||
raise ValueError("ttl_seconds out of bounds")
|
||||
runtime_path = issue_runtime_path(args.root, args.owner, args.repository, args.issue, args.run_id)
|
||||
self.root = _abs(args.root, "root")
|
||||
self.handle = _abs(args.handle, "handle")
|
||||
self.sock_path = _abs(args.socket, "socket")
|
||||
self.handle = runtime_path / "broker.handle"
|
||||
self.sock_path = self.handle.parent / "broker.sock"
|
||||
supplied_handle = getattr(args, "handle", None)
|
||||
supplied_socket = getattr(args, "socket", None)
|
||||
if supplied_handle is not None and os.fspath(supplied_handle) != str(self.handle):
|
||||
raise ValueError("handle path must be canonical")
|
||||
if supplied_socket is not None and os.fspath(supplied_socket) != str(self.sock_path):
|
||||
raise ValueError("socket path must be canonical")
|
||||
locks = self.root / "repositories" / args.owner / args.repository / "locks"
|
||||
_safe_dir(locks, "repository locks")
|
||||
_safe_dir(self.handle.parent, "handle")
|
||||
_safe_dir(self.sock_path.parent, "socket")
|
||||
self.token = secrets.token_hex(32)
|
||||
self.deadline = time.monotonic() + args.ttl_seconds
|
||||
self.set = ManagedLockSet.for_issue(self.root, args.owner, args.repository, args.issue, timeout=args.acquire_timeout)
|
||||
@@ -337,6 +343,14 @@ class Broker:
|
||||
signal.signal(signal.SIGTERM, lambda *_: setattr(self, "stopping", True))
|
||||
signal.signal(signal.SIGINT, lambda *_: setattr(self, "stopping", True))
|
||||
self.set.issue.write_metadata(self.meta)
|
||||
with self.set.repository_scope():
|
||||
runtime = prepare_issue_runtime(self.root, self.meta["owner"], self.meta["repository"],
|
||||
self.meta["issue"], self.meta["run_id"], lock_set=self.set)
|
||||
try:
|
||||
runtime_fd = runtime.fd
|
||||
self.socket_parent_fd = os.dup(runtime_fd)
|
||||
finally:
|
||||
os.close(runtime.fd)
|
||||
try:
|
||||
_write_handle(self.handle, self.state, exclusive=True,
|
||||
on_created=self._record_handle_identity)
|
||||
@@ -346,7 +360,9 @@ class Broker:
|
||||
if (st.st_dev, st.st_ino) != self.handle_identity:
|
||||
raise ValueError("created handle identity changed")
|
||||
self.handle_created = True
|
||||
self.socket_parent_fd, socket_name = _open_parent(self.sock_path)
|
||||
if self.socket_parent_fd is None:
|
||||
raise ValueError("runtime directory unavailable")
|
||||
socket_name = self.sock_path.name
|
||||
try:
|
||||
os.stat(socket_name, dir_fd=self.socket_parent_fd, follow_symlinks=False)
|
||||
raise ValueError("socket path already exists")
|
||||
@@ -404,13 +420,26 @@ def request_handle(handle_path: str | Path, action: str, ttl: float | None = Non
|
||||
handle = _abs(str(handle_path), "handle")
|
||||
data = _read_handle(handle)
|
||||
sock_path = _abs(data["socket"], "socket")
|
||||
req = {"action": action, "token": data["token"]}
|
||||
if ttl is not None:
|
||||
req["ttl_seconds"] = ttl
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
||||
sock.settimeout(3); sock.connect(str(sock_path)); sock.sendall((json.dumps(req) + "\n").encode())
|
||||
with sock.makefile("rb") as response_file:
|
||||
response = json.loads(response_file.readline())
|
||||
parent_fd, name = _open_parent(sock_path)
|
||||
try:
|
||||
parent_st = os.fstat(parent_fd)
|
||||
if (parent_st.st_uid != os.getuid() or stat.S_IMODE(parent_st.st_mode) != 0o700):
|
||||
raise ValueError("unsafe socket parent")
|
||||
st = os.stat(name, dir_fd=parent_fd, follow_symlinks=False)
|
||||
if (not stat.S_ISSOCK(st.st_mode) or st.st_uid != os.getuid()
|
||||
or stat.S_IMODE(st.st_mode) != 0o600):
|
||||
raise ValueError("unsafe socket")
|
||||
req = {"action": action, "token": data["token"]}
|
||||
if ttl is not None:
|
||||
req["ttl_seconds"] = ttl
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
||||
sock.settimeout(3)
|
||||
sock.connect(f"/proc/self/fd/{parent_fd}/{name}")
|
||||
sock.sendall((json.dumps(req) + "\n").encode())
|
||||
with sock.makefile("rb") as response_file:
|
||||
response = json.loads(response_file.readline())
|
||||
finally:
|
||||
os.close(parent_fd)
|
||||
if not isinstance(response, dict):
|
||||
raise ValueError("invalid broker response")
|
||||
return response
|
||||
@@ -444,7 +473,7 @@ def parser() -> argparse.ArgumentParser:
|
||||
p = argparse.ArgumentParser()
|
||||
sub = p.add_subparsers(dest="command", required=True)
|
||||
s = sub.add_parser("serve")
|
||||
s.add_argument("--root", required=True); s.add_argument("--owner", required=True); s.add_argument("--repository", required=True); s.add_argument("--issue", required=True, type=int); s.add_argument("--role", required=True); s.add_argument("--run-id", required=True); s.add_argument("--handle", required=True); s.add_argument("--socket", required=True); s.add_argument("--ttl-seconds", required=True, type=float); s.add_argument("--acquire-timeout", type=float, default=0.0)
|
||||
s.add_argument("--root", required=True); s.add_argument("--owner", required=True); s.add_argument("--repository", required=True); s.add_argument("--issue", required=True, type=int); s.add_argument("--role", required=True); s.add_argument("--run-id", required=True); s.add_argument("--handle"); s.add_argument("--socket"); s.add_argument("--ttl-seconds", required=True, type=float); s.add_argument("--acquire-timeout", type=float, default=0.0)
|
||||
r = sub.add_parser("request")
|
||||
r.add_argument("--handle", required=True); r.add_argument("--action", required=True, choices=["status", "renew", "acquire_repository", "release_repository", "release"]); r.add_argument("--ttl-seconds", type=float)
|
||||
check = sub.add_parser("probe")
|
||||
|
||||
Reference in New Issue
Block a user