fix: remove explicit repo mount from pi-agent manifest and derive workspace name from remote URL

The pi-agent manifest still declared an explicit repo mount with
{{WORKSPACE_NAME}}, making the mount target dependent on tool config. The
instance service now synthesizes the repo mount, so the manifest no longer
needs the explicit mount.

- Add Alembic migration 2026_06_15_090500 to remove the source_type: repo
  mount from the built-in pi-agent manifest
- Add _get_repository_mount_name() helper to derive the workspace directory
  name from the repository remote URL (matching git clone behavior) and
  fall back to the user-provided repository name
- Use the helper for WORKSPACE_NAME/REPO_NAME in manifest, legacy dockerfile,
  and legacy compose template paths
- Update unit tests for the new migration and helper

Quality gates:
- pytest tests/unit: 218 passed
- ruff: clean on changed files
- mypy: clean on changed files
- alembic heads: single head
This commit is contained in:
Developer
2026-06-15 09:10:05 +00:00
parent f0ae9483f3
commit 6e33e8e4e9
29 changed files with 245 additions and 62 deletions
+29 -5
View File
@@ -73,11 +73,29 @@ from src.services.shared.readiness_probe import execute_probe
from src.services.shared.ssh_keys import prepare_ssh_key_files
from src.services.instance.event_bus import InstanceEventBus
from src.services.instance.lifecycle_hooks import publish_lifecycle_event
from src.utils.git_url_parser import extract_base_repo_url
logger = logging.getLogger(__name__)
_event_bus = InstanceEventBus()
def _get_repository_mount_name(repo: GitRepository) -> str:
"""Return the directory name a standard git clone would create.
Prefers the repository name parsed from the remote URL so the container
mount matches what users expect from ``git clone``. Falls back to the
user-provided repository name when no remote URL is available.
"""
if repo.remote_url:
base_url = extract_base_repo_url(repo.remote_url) or repo.remote_url
name = base_url.rstrip("/").split("/")[-1]
if name.endswith(".git"):
name = name[:-4]
if name:
return name
return repo.name
def _chown_path(path: str, uid: int, gid: int) -> None:
"""Recursively chown a path, suppressing permission errors."""
try:
@@ -889,8 +907,13 @@ async def prepare_manifest_instance(
# Use the repository name for the workspace/repo mount target, not the
# directory name of a workspace/clone path (which may be "main" or similar).
# Prefer the name parsed from the remote URL so it matches a standard clone.
repo = await session.get(GitRepository, instance.repository_id)
repo_name = repo.name if repo else os.path.basename(os.path.normpath(repo_path))
repo_name = (
_get_repository_mount_name(repo)
if repo
else os.path.basename(os.path.normpath(repo_path))
)
variables = {
"IMAGE_TAG": image_tag,
"INSTANCE_NAME": instance.name.lower(),
@@ -1052,7 +1075,8 @@ async def create_tool_instance(
)
home_dir = tool_type.home_directory or "/home/user"
workspace_target = f"{home_dir}/{repo.name}"
mount_name = _get_repository_mount_name(repo)
workspace_target = f"{home_dir}/{mount_name}"
compose_content = f"""version: "3.8"\nservices:
app:
@@ -1094,8 +1118,8 @@ async def create_tool_instance(
"INSTANCE_DIR": instance_dir,
"WORKSPACE_PATH": repo_path,
"REPO_PATH": repo_path,
"REPO_NAME": repo.name,
"WORKSPACE_NAME": repo.name,
"REPO_NAME": _get_repository_mount_name(repo),
"WORKSPACE_NAME": _get_repository_mount_name(repo),
"SSH_PATH": "",
"TOOL_PORT": tool_port,
"EXTRA_ENV": {},
@@ -1116,7 +1140,7 @@ async def create_tool_instance(
"TOOL_PORT": tool_port,
"USER_ID": str(user_id),
"PROJECT_ID": str(project_id),
"WORKSPACE_NAME": repo.name,
"WORKSPACE_NAME": _get_repository_mount_name(repo),
"HOME_DIRECTORY": tool_type.home_directory or "/home/user",
}
compose_content = render_compose_template(tool_type.compose_template, variables)