feat(harness): allocate canonical issue-scoped broker runtime
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
"""Canonical, private Issue runtime allocation (not cache/worktree admission)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import grp
|
||||
import json
|
||||
import os
|
||||
import pwd
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
if __package__ in (None, ''):
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
|
||||
from harness.lib.managed_lock import ManagedLockSet, validate_held_flock
|
||||
|
||||
_IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,254}")
|
||||
_RUN = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,127}")
|
||||
_DIR_FLAGS = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC
|
||||
|
||||
|
||||
def _identity(root, owner, repository, issue, run_id=None):
|
||||
raw = os.fspath(root)
|
||||
if not isinstance(raw, str) or not raw.startswith('/') or '\x00' in raw:
|
||||
raise ValueError('invalid harness root')
|
||||
if any(part in {'', '.', '..'} for part in raw.split('/')[1:]):
|
||||
raise ValueError('invalid harness root')
|
||||
for value in (owner, repository):
|
||||
if not isinstance(value, str) or not _IDENTIFIER.fullmatch(value):
|
||||
raise ValueError('invalid repository identity')
|
||||
if isinstance(issue, bool) or not isinstance(issue, int) or issue <= 0:
|
||||
raise ValueError('invalid Issue')
|
||||
if run_id is not None and (not isinstance(run_id, str) or not _RUN.fullmatch(run_id)):
|
||||
raise ValueError('invalid workflow run id')
|
||||
return Path(raw)
|
||||
|
||||
|
||||
def issue_runtime_path(root, owner, repository, issue, run_id):
|
||||
root = _identity(root, owner, repository, issue, run_id)
|
||||
return root / 'repositories' / owner / repository / 'worktrees' / f'issue-{issue}' / 'runtime' / run_id
|
||||
|
||||
|
||||
def _open_absolute(path):
|
||||
fd = os.open('/', _DIR_FLAGS)
|
||||
try:
|
||||
for part in path.parts[1:]:
|
||||
child = os.open(part, _DIR_FLAGS, dir_fd=fd)
|
||||
os.close(fd)
|
||||
fd = child
|
||||
return fd
|
||||
except BaseException:
|
||||
os.close(fd)
|
||||
raise
|
||||
|
||||
|
||||
def _private(st):
|
||||
if not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o700:
|
||||
raise ValueError('unsafe private directory')
|
||||
|
||||
|
||||
def _repository_container(st):
|
||||
mode = stat.S_IMODE(st.st_mode)
|
||||
if not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid() or mode & 0o002:
|
||||
raise ValueError('unsafe repositories directory')
|
||||
if mode & 0o020:
|
||||
try:
|
||||
group = grp.getgrgid(st.st_gid)
|
||||
members = {pwd.getpwnam(name).pw_uid for name in group.gr_mem}
|
||||
members.update(user.pw_uid for user in pwd.getpwall() if user.pw_gid == st.st_gid)
|
||||
except KeyError as exc:
|
||||
raise ValueError('unresolved repositories group') from exc
|
||||
if members != {os.getuid()}:
|
||||
raise ValueError('unsafe repositories group')
|
||||
|
||||
|
||||
def _existing(parent, name, check=_private):
|
||||
child = os.open(name, _DIR_FLAGS, dir_fd=parent)
|
||||
try:
|
||||
check(os.fstat(child))
|
||||
named = os.stat(name, dir_fd=parent, follow_symlinks=False)
|
||||
opened = os.fstat(child)
|
||||
if (named.st_dev, named.st_ino) != (opened.st_dev, opened.st_ino):
|
||||
raise ValueError('directory identity changed')
|
||||
return child
|
||||
except BaseException:
|
||||
os.close(child)
|
||||
raise
|
||||
|
||||
|
||||
def _cache_fd(root, owner, repository):
|
||||
fd = _open_absolute(root)
|
||||
try:
|
||||
if os.fstat(fd).st_mode & 0o002:
|
||||
raise ValueError('world-writable harness root')
|
||||
for name, check in [('repositories', _repository_container), (owner, _private), (repository, _private)]:
|
||||
child = _existing(fd, name, check)
|
||||
os.close(fd)
|
||||
fd = child
|
||||
return fd
|
||||
except BaseException:
|
||||
os.close(fd)
|
||||
raise
|
||||
|
||||
|
||||
def _new_child(parent, name, created):
|
||||
os.mkdir(name, 0o700, dir_fd=parent)
|
||||
initial = os.stat(name, dir_fd=parent, follow_symlinks=False)
|
||||
created.append((os.dup(parent), name, (initial.st_dev, initial.st_ino)))
|
||||
# Correct only a directory just created by this call, including restrictive umask.
|
||||
if not stat.S_ISDIR(initial.st_mode) or initial.st_uid != os.getuid():
|
||||
raise ValueError('new directory identity changed')
|
||||
os.chmod(name, 0o700, dir_fd=parent, follow_symlinks=False)
|
||||
fd = _existing(parent, name)
|
||||
opened = os.fstat(fd)
|
||||
if (opened.st_dev, opened.st_ino) != (initial.st_dev, initial.st_ino):
|
||||
os.close(fd)
|
||||
raise ValueError('new directory identity changed')
|
||||
return fd
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimeDirectory:
|
||||
path: Path
|
||||
fd: int # Caller owns this descriptor and must close it.
|
||||
|
||||
|
||||
def prepare_issue_runtime(root, owner, repository, issue, run_id, *, lock_set):
|
||||
path = issue_runtime_path(root, owner, repository, issue, run_id)
|
||||
expected = ManagedLockSet.paths_for_issue(root, owner, repository, issue)
|
||||
if (not isinstance(lock_set, ManagedLockSet) or not lock_set.issue.held or not lock_set.repository.held
|
||||
or (lock_set.issue.path, lock_set.repository.path) != expected):
|
||||
raise ValueError('matching Issue and repository leases required')
|
||||
for lock in (lock_set.issue, lock_set.repository):
|
||||
lock_fd = lock.fd
|
||||
if lock_fd is None or not validate_held_flock(lock_fd, lock.path):
|
||||
raise ValueError('kernel lease unavailable')
|
||||
parent = _open_absolute(lock.path.parent)
|
||||
try:
|
||||
_private(os.fstat(parent))
|
||||
named = os.stat(lock.path.name, dir_fd=parent, follow_symlinks=False)
|
||||
opened = os.fstat(lock_fd)
|
||||
if (named.st_dev, named.st_ino) != (opened.st_dev, opened.st_ino):
|
||||
raise ValueError('kernel lease path identity mismatch')
|
||||
finally:
|
||||
os.close(parent)
|
||||
fd = _cache_fd(Path(root), owner, repository)
|
||||
created = []
|
||||
success = False
|
||||
try:
|
||||
for name in ('worktrees', f'issue-{issue}', 'runtime'):
|
||||
try:
|
||||
child = _existing(fd, name)
|
||||
except FileNotFoundError:
|
||||
child = _new_child(fd, name, created)
|
||||
os.close(fd)
|
||||
fd = child
|
||||
# Runs are immutable identities; even a safe, empty stale run is a collision.
|
||||
try:
|
||||
fd_run = _new_child(fd, run_id, created)
|
||||
except FileExistsError as exc:
|
||||
raise ValueError('runtime run already exists') from exc
|
||||
os.close(fd)
|
||||
fd = fd_run
|
||||
success = True
|
||||
return RuntimeDirectory(path, fd)
|
||||
finally:
|
||||
if not success:
|
||||
os.close(fd)
|
||||
for parent, name, identity in reversed(created):
|
||||
try:
|
||||
current = os.stat(name, dir_fd=parent, follow_symlinks=False)
|
||||
if (current.st_dev, current.st_ino) == identity and stat.S_ISDIR(current.st_mode):
|
||||
os.rmdir(name, dir_fd=parent) # Empty, owned inode only; never recursive.
|
||||
except OSError:
|
||||
pass
|
||||
for parent, _, _ in created:
|
||||
os.close(parent)
|
||||
|
||||
|
||||
def inspect_issue_worktree_admission(root, owner, repository, issue):
|
||||
"""Classify container shape; caller still authenticates state/base/registration.
|
||||
|
||||
This read-only check is used under Issue + repository leases. It does not
|
||||
authorize Git writes or declare an existing checkout valid.
|
||||
"""
|
||||
root = _identity(root, owner, repository, issue)
|
||||
fd = _cache_fd(root, owner, repository)
|
||||
issue_fd = None
|
||||
try:
|
||||
bare = root / 'repositories' / owner / repository / 'repository.git'
|
||||
bare_fd = os.open('repository.git', _DIR_FLAGS, dir_fd=fd)
|
||||
try:
|
||||
bare_st = os.fstat(bare_fd)
|
||||
if bare_st.st_uid != os.getuid() or bare_st.st_mode & 0o022:
|
||||
raise ValueError('unsafe bare repository')
|
||||
result = subprocess.run(['git', '--git-dir', f'/proc/self/fd/{bare_fd}', 'worktree', 'list', '--porcelain', '-z'],
|
||||
pass_fds=(bare_fd,), capture_output=True, text=True, timeout=30, check=False)
|
||||
finally:
|
||||
os.close(bare_fd)
|
||||
if result.returncode:
|
||||
raise ValueError('Git registration unavailable')
|
||||
checkout = bare.parent / 'worktrees' / f'issue-{issue}' / 'checkout'
|
||||
registrations = [field[9:] for field in result.stdout.split('\x00') if field.startswith('worktree ')]
|
||||
registered = registrations.count(str(checkout))
|
||||
try:
|
||||
worktrees = _existing(fd, 'worktrees')
|
||||
except FileNotFoundError:
|
||||
return 'invalid' if registered else 'absent'
|
||||
try:
|
||||
try:
|
||||
issue_fd = _existing(worktrees, f'issue-{issue}')
|
||||
except FileNotFoundError:
|
||||
return 'invalid' if registered else 'absent'
|
||||
finally:
|
||||
os.close(worktrees)
|
||||
entries = set(os.listdir(issue_fd))
|
||||
for name in entries & {'runtime', 'verification'}:
|
||||
child = _existing(issue_fd, name)
|
||||
os.close(child)
|
||||
if entries <= {'runtime', 'verification'}:
|
||||
return 'invalid' if registered else 'runtime-only'
|
||||
if entries != (entries & {'runtime', 'verification'}) | {'checkout', 'state.json'} or registered != 1:
|
||||
return 'invalid'
|
||||
source = os.stat('checkout', dir_fd=issue_fd, follow_symlinks=False)
|
||||
state = os.stat('state.json', dir_fd=issue_fd, follow_symlinks=False)
|
||||
if (not stat.S_ISDIR(source.st_mode) or source.st_uid != os.getuid() or source.st_mode & 0o022
|
||||
or not stat.S_ISREG(state.st_mode) or state.st_uid != os.getuid()
|
||||
or stat.S_IMODE(state.st_mode) != 0o600 or state.st_nlink != 1):
|
||||
return 'invalid'
|
||||
return 'checkout-candidate'
|
||||
finally:
|
||||
if issue_fd is not None:
|
||||
os.close(issue_fd)
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def main():
|
||||
# Direct-script bootstrap is below; imports are delayed to avoid broker cycles.
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('command', choices=['inspect-worktree'])
|
||||
for name in ('root', 'owner', 'repository', 'handle'):
|
||||
parser.add_argument('--' + name, required=True)
|
||||
parser.add_argument('--issue', required=True, type=int)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
from harness.lib import managed_lock_broker as broker, verification_root
|
||||
root = _identity(args.root, args.owner, args.repository, args.issue)
|
||||
verification_root._lease(Path(args.handle), root, args.owner, args.repository, args.issue)
|
||||
if broker.request_handle(args.handle, 'status').get('status') != 'repository-held':
|
||||
raise ValueError('repository lease required')
|
||||
shape = inspect_issue_worktree_admission(root, args.owner, args.repository, args.issue)
|
||||
print(json.dumps({'container': shape}))
|
||||
return 1 if shape == 'invalid' else 0
|
||||
except (ValueError, OSError, subprocess.SubprocessError):
|
||||
print(json.dumps({'error': 'worktree-container-unavailable'}))
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user