fix: git repo mount race and path resolution
- Include branch in git-mount clone dir hash so different branches of the same repo get separate directories and no longer race. - Resolve profile working_directory before git mounts so relative targets are not silently skipped. - Fall back to tool_type.home_directory for non-manifest tools. - Fix glob target calculation to avoid duplicate directory segment. - Add exc_info logging for git mount clone failures.
This commit is contained in:
@@ -204,7 +204,12 @@ def clone_git_repo(
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
url_hash = hashlib.md5(remote_url.encode()).hexdigest()[:12]
|
||||
# Include the branch in the hash so different branches of the same repo
|
||||
# get separate clone directories and cannot race each other.
|
||||
branch_segment = branch or "default"
|
||||
url_hash = hashlib.md5(
|
||||
f"{remote_url}:{branch_segment}".encode()
|
||||
).hexdigest()[:12]
|
||||
repo_name = remote_url.split("/")[-1].replace(".git", "") or "repo"
|
||||
clone_dir = os.path.join(clone_parent, "git-mounts", f"{repo_name}-{url_hash}")
|
||||
repo_path = os.path.join(clone_dir, "repo-clone")
|
||||
@@ -303,8 +308,20 @@ def resolve_git_mount_mappings(
|
||||
# Single match: mount directly to target_path
|
||||
mount_target = final_target
|
||||
else:
|
||||
# Multiple matches: append relative path to target
|
||||
rel_path = os.path.relpath(matched_path, repo_path)
|
||||
# Multiple matches: append the path relative to the glob's base
|
||||
# directory so `packages/*` → `/app/packages` yields
|
||||
# `/app/packages/api` instead of `/app/packages/packages/api`.
|
||||
first_glob_idx = min(
|
||||
(source_path.find(c) for c in "*?[" if c in source_path),
|
||||
default=len(source_path),
|
||||
)
|
||||
base_relative = os.path.dirname(source_path[: first_glob_idx + 1])
|
||||
base_full = (
|
||||
os.path.join(repo_path, base_relative)
|
||||
if base_relative
|
||||
else repo_path
|
||||
)
|
||||
rel_path = os.path.relpath(matched_path, base_full)
|
||||
mount_target = os.path.join(final_target, rel_path)
|
||||
|
||||
volume_mounts.append(
|
||||
@@ -357,7 +374,14 @@ async def resolve_single_git_mount(
|
||||
repo_path = await asyncio.to_thread(
|
||||
clone_git_repo, remote_url, branch, instance_dir
|
||||
)
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Git mount clone failed for %s (branch=%s): %s",
|
||||
remote_url,
|
||||
branch,
|
||||
exc,
|
||||
exc_info=True,
|
||||
)
|
||||
return []
|
||||
|
||||
# Resolve all mappings from the cloned repo
|
||||
@@ -1241,7 +1265,7 @@ async def start_tool_instance(
|
||||
|
||||
# Fetch tool type early to determine home directory and container user
|
||||
tool_type = await session.get(ToolType, instance.tool_type_id)
|
||||
home_dir = "/root"
|
||||
home_dir = tool_type.home_directory if tool_type and tool_type.home_directory else "/root"
|
||||
container_uid = 0
|
||||
container_gid = 0
|
||||
if tool_type and tool_type.definition_type == "manifest" and tool_type.manifest_id:
|
||||
@@ -1259,16 +1283,22 @@ async def start_tool_instance(
|
||||
deep_merge(dict(base_def.manifest), manifest)
|
||||
)
|
||||
home_dir = get_manifest_home_dir(manifest)
|
||||
runtime = manifest.get("runtime", {})
|
||||
if runtime.get("working_dir"):
|
||||
working_directory = expand_container_path(
|
||||
runtime["working_dir"], home_dir
|
||||
)
|
||||
user_cfg = manifest.get("user")
|
||||
if user_cfg:
|
||||
container_uid = user_cfg.get("uid", 0)
|
||||
container_gid = user_cfg.get("gid", 0)
|
||||
logger.debug(
|
||||
"Manifest user resolved for instance %s: uid=%s, gid=%s, home=%s",
|
||||
"Manifest user resolved for instance %s: uid=%s, gid=%s, home=%s, working_directory=%s",
|
||||
instance.id,
|
||||
container_uid,
|
||||
container_gid,
|
||||
home_dir,
|
||||
working_directory,
|
||||
)
|
||||
|
||||
# Apply selected config profile if any
|
||||
@@ -1281,6 +1311,16 @@ async def start_tool_instance(
|
||||
profile_env, profile_files, profile_mounts, profile_hints = (
|
||||
apply_resolved_profile(instance_dir, resolved, home_dir)
|
||||
)
|
||||
# Profile hints override the manifest/tool defaults, and git mounts
|
||||
# need the final working directory to resolve relative target paths.
|
||||
if profile_hints.get("start_command"):
|
||||
start_command = profile_hints["start_command"]
|
||||
if profile_hints.get("working_directory"):
|
||||
working_directory = expand_container_path(
|
||||
profile_hints["working_directory"], home_dir
|
||||
)
|
||||
if profile_hints.get("port_override"):
|
||||
port_override = profile_hints["port_override"]
|
||||
env_vars.update(profile_env)
|
||||
config_files.update(profile_files)
|
||||
extra_volumes.extend(profile_mounts)
|
||||
@@ -1288,12 +1328,6 @@ async def start_tool_instance(
|
||||
session, resolved, instance_dir, working_directory, home_dir
|
||||
)
|
||||
extra_volumes.extend(git_mount_volumes)
|
||||
if profile_hints.get("start_command"):
|
||||
start_command = profile_hints["start_command"]
|
||||
if profile_hints.get("working_directory"):
|
||||
working_directory = profile_hints["working_directory"]
|
||||
if profile_hints.get("port_override"):
|
||||
port_override = profile_hints["port_override"]
|
||||
logger.debug(
|
||||
"Applied config profile %s to instance %s (env=%d, files=%d, mounts=%d, git_mounts=%d)",
|
||||
resolved.profile_name,
|
||||
|
||||
Reference in New Issue
Block a user