fix: task files endpoint resolves tenant docker worktrees
GET /tasks/{id}/files previously returned 409 for all docker tasks
on the grounds that tenant docker shares /app/work per project.
Production inspection shows the worker creates a per-task
host-worktrees/<task-id> subdirectory under the tenant work dir
(62 such dirs under the wwww tenant), so per-task reads are
attributable after all: the endpoint now resolves tenant docker
tasks via tenants/<project>/work/host-worktrees/<task-id> (local
worktree as fallback), never serves the shared /app/work mount
itself, and still 409s cloud/cross-host tasks. Docs and the
harness2_tasks boundary notes are updated to match.
This commit is contained in:
1 parent
d4a517fc7e
commit
4cbdc41e20
4 files changed
+76
-25
No files matched your search
+40
-13
@@ -445,6 +445,38 @@ def _thread_worktree_for_task(server, task):
|
||||
return os.path.join(base, safe)
|
||||
|
||||
|
||||
def _task_worktree_for_files(server, task):
|
||||
"""文件端点用的任务 worktree 解析。
|
||||
|
||||
本地任务走 _thread_worktree_for_task;租户 Docker 任务走
|
||||
tenants_root/<安全项目名>/work/host-worktrees/<安全任务名>——
|
||||
worker 在租户 work 目录下按任务建子目录(生产实查 wwww 租户
|
||||
下 62 个任务目录皆如此,安全名规则与 worker 侧一致)。绝不
|
||||
直接 serves 共享的 /app/work 挂载点本身,只 serves 任务级子
|
||||
目录,所以不存在按任务错指。cloud 任务与跨机 worktree 一律
|
||||
409;两个候选路径都没有时 404(任务未启动或目录已清理)。
|
||||
返回 (path, None) 或 (None, (status, msg))。
|
||||
"""
|
||||
if task.get("executor") == "cloud":
|
||||
return None, (409, "cloud task worktrees are not on this host")
|
||||
worktree_host = task.get("worktree_host")
|
||||
if worktree_host and worktree_host != socket.gethostname():
|
||||
return None, (409, "task worktree is on host %s;"
|
||||
" files must be read there" % worktree_host)
|
||||
safe_task = re.sub(r"[^A-Za-z0-9_.-]", "-", str(task["id"]))
|
||||
safe_project = _tenant_safe_project_name(task.get("project_id"))
|
||||
if safe_project:
|
||||
tenant_path = os.path.join(
|
||||
_tenants_root_for(server), safe_project, "work",
|
||||
"host-worktrees", safe_task)
|
||||
if os.path.isdir(tenant_path):
|
||||
return tenant_path, None
|
||||
local_path = _thread_worktree_for_task(server, task)
|
||||
if os.path.isdir(local_path):
|
||||
return local_path, None
|
||||
return None, (404, "no local worktree for this task")
|
||||
|
||||
|
||||
def _safe_worktree_path(worktree, rel):
|
||||
"""worktree 内相对路径的安全解析:空串返回 worktree 本身;
|
||||
绝对路径、.. 逃逸、符号链接逃逸一律返回 None。realpath 后
|
||||
@@ -4753,13 +4785,12 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
})
|
||||
|
||||
def _task_files(self, user_id, task_id, query):
|
||||
"""GET /tasks/{id}/files:读本地任务 worktree 里的文件。
|
||||
"""GET /tasks/{id}/files:读任务 worktree 里的文件。
|
||||
|
||||
只做 local 执行器任务(复用 thread 操作的执行器门):
|
||||
Docker 任务的 worktree 在容器内、且租户 Docker 下 /app/work
|
||||
是整项目共享目录,按任务归属读文件会错指,一律 409 明
|
||||
说;cloud 任务与跨机 worktree 同理。executor 为空的老
|
||||
任务放行(与 thread 门同款),解析不到 worktree 时 404。
|
||||
解析见 _task_worktree_for_files:本地任务走本地 worktree,
|
||||
租户 Docker 任务走 tenants 下的任务级子目录(只 serves
|
||||
host-worktrees/<任务id>,绝不 serves 共享的 /app/work 本
|
||||
身);cloud 任务与跨机 worktree 一律 409,解析不到时 404。
|
||||
|
||||
?path=<相对路径>:不传列 worktree 顶层(递归、最多 200
|
||||
条,只含文件路径与大小,不跟符号链接);传目录列该目录
|
||||
@@ -4772,13 +4803,9 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
if err is not None:
|
||||
self._send_json(err[0], {"error": err[1]})
|
||||
return
|
||||
guard = self._thread_task_guard(task)
|
||||
if guard is not None:
|
||||
self._send_json(guard[0], {"error": guard[1]})
|
||||
return
|
||||
worktree = _thread_worktree_for_task(self.server, task)
|
||||
if not os.path.isdir(worktree):
|
||||
self._send_json(404, {"error": "no local worktree for this task"})
|
||||
worktree, werr = _task_worktree_for_files(self.server, task)
|
||||
if werr is not None:
|
||||
self._send_json(werr[0], {"error": werr[1]})
|
||||
return
|
||||
rel = (query.get("path") or [""])[0]
|
||||
target = _safe_worktree_path(worktree, rel)
|
||||
|
||||
Reference in new issue
Block a user