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
|
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"
|
repo_name = remote_url.split("/")[-1].replace(".git", "") or "repo"
|
||||||
clone_dir = os.path.join(clone_parent, "git-mounts", f"{repo_name}-{url_hash}")
|
clone_dir = os.path.join(clone_parent, "git-mounts", f"{repo_name}-{url_hash}")
|
||||||
repo_path = os.path.join(clone_dir, "repo-clone")
|
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
|
# Single match: mount directly to target_path
|
||||||
mount_target = final_target
|
mount_target = final_target
|
||||||
else:
|
else:
|
||||||
# Multiple matches: append relative path to target
|
# Multiple matches: append the path relative to the glob's base
|
||||||
rel_path = os.path.relpath(matched_path, repo_path)
|
# 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)
|
mount_target = os.path.join(final_target, rel_path)
|
||||||
|
|
||||||
volume_mounts.append(
|
volume_mounts.append(
|
||||||
@@ -357,7 +374,14 @@ async def resolve_single_git_mount(
|
|||||||
repo_path = await asyncio.to_thread(
|
repo_path = await asyncio.to_thread(
|
||||||
clone_git_repo, remote_url, branch, instance_dir
|
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 []
|
return []
|
||||||
|
|
||||||
# Resolve all mappings from the cloned repo
|
# 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
|
# Fetch tool type early to determine home directory and container user
|
||||||
tool_type = await session.get(ToolType, instance.tool_type_id)
|
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_uid = 0
|
||||||
container_gid = 0
|
container_gid = 0
|
||||||
if tool_type and tool_type.definition_type == "manifest" and tool_type.manifest_id:
|
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)
|
deep_merge(dict(base_def.manifest), manifest)
|
||||||
)
|
)
|
||||||
home_dir = get_manifest_home_dir(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")
|
user_cfg = manifest.get("user")
|
||||||
if user_cfg:
|
if user_cfg:
|
||||||
container_uid = user_cfg.get("uid", 0)
|
container_uid = user_cfg.get("uid", 0)
|
||||||
container_gid = user_cfg.get("gid", 0)
|
container_gid = user_cfg.get("gid", 0)
|
||||||
logger.debug(
|
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,
|
instance.id,
|
||||||
container_uid,
|
container_uid,
|
||||||
container_gid,
|
container_gid,
|
||||||
home_dir,
|
home_dir,
|
||||||
|
working_directory,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply selected config profile if any
|
# Apply selected config profile if any
|
||||||
@@ -1281,6 +1311,16 @@ async def start_tool_instance(
|
|||||||
profile_env, profile_files, profile_mounts, profile_hints = (
|
profile_env, profile_files, profile_mounts, profile_hints = (
|
||||||
apply_resolved_profile(instance_dir, resolved, home_dir)
|
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)
|
env_vars.update(profile_env)
|
||||||
config_files.update(profile_files)
|
config_files.update(profile_files)
|
||||||
extra_volumes.extend(profile_mounts)
|
extra_volumes.extend(profile_mounts)
|
||||||
@@ -1288,12 +1328,6 @@ async def start_tool_instance(
|
|||||||
session, resolved, instance_dir, working_directory, home_dir
|
session, resolved, instance_dir, working_directory, home_dir
|
||||||
)
|
)
|
||||||
extra_volumes.extend(git_mount_volumes)
|
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(
|
logger.debug(
|
||||||
"Applied config profile %s to instance %s (env=%d, files=%d, mounts=%d, git_mounts=%d)",
|
"Applied config profile %s to instance %s (env=%d, files=%d, mounts=%d, git_mounts=%d)",
|
||||||
resolved.profile_name,
|
resolved.profile_name,
|
||||||
|
|||||||
Reference in New Issue
Block a user