WIP: working-copies backend cleanup
- Remove clone_mode/branch from API responses and make DB columns nullable - Remove legacy clone-mode branches from create_tool_instance - Add WORKSPACE_PATH compose variable alongside REPO_PATH - Add workspace migration helpers in WorkspaceManager Remaining: POST /workspaces/:id/instances, frontend clone_mode cleanup, tests
This commit is contained in:
@@ -830,8 +830,6 @@ async def prepare_manifest_instance(
|
||||
|
||||
# Prepare SSH path for mount resolution
|
||||
ssh_path = ""
|
||||
if instance.clone_mode == "clone":
|
||||
ssh_path = os.path.join(instance_dir, ".ssh")
|
||||
|
||||
# Resolve git mount variables from config profile
|
||||
git_mount_vars = {}
|
||||
@@ -848,6 +846,7 @@ async def prepare_manifest_instance(
|
||||
"IMAGE_TAG": image_tag,
|
||||
"INSTANCE_NAME": instance.name.lower(),
|
||||
"INSTANCE_DIR": instance_dir,
|
||||
"WORKSPACE_PATH": repo_path,
|
||||
"REPO_PATH": repo_path,
|
||||
"SSH_PATH": ssh_path,
|
||||
"TOOL_PORT": instance.port or 0,
|
||||
@@ -922,13 +921,6 @@ async def create_tool_instance(
|
||||
if workspace.repo_id != repo_id:
|
||||
raise ValueError("workspace does not belong to this repository")
|
||||
|
||||
# Validate clone mode requirements (legacy path)
|
||||
if data.clone_mode == "clone" and not workspace:
|
||||
if not repo.remote_url:
|
||||
raise ValueError("repository does not have a remote URL for cloning")
|
||||
if not repo.ssh_key_id:
|
||||
raise ValueError("repository must have an SSH key assigned for clone mode")
|
||||
|
||||
# Generate unique name
|
||||
instance_name = f"{tool_type.name}-{repo.name}-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
@@ -970,71 +962,13 @@ async def create_tool_instance(
|
||||
# Find free port
|
||||
tool_port = find_free_port()
|
||||
|
||||
# Determine repo path based on workspace or clone mode
|
||||
# Determine the source path to mount. Workspaces are the canonical path;
|
||||
# legacy instances without a workspace fall back to the repository path.
|
||||
if workspace:
|
||||
repo_path = workspace.path
|
||||
elif data.clone_mode == "clone":
|
||||
ssh_key = await session.get(SSHKey, repo.ssh_key_id)
|
||||
if ssh_key is None:
|
||||
raise ValueError("repository SSH key not found")
|
||||
|
||||
ssh_key_path = None
|
||||
try:
|
||||
ssh_dir = prepare_ssh_key_files(instance_dir, ssh_key)
|
||||
ssh_key_path = os.path.join(ssh_dir, "id_ed25519")
|
||||
|
||||
clone_path = clone_repository(
|
||||
remote_url=repo.remote_url,
|
||||
ssh_key_path=ssh_key_path,
|
||||
instance_dir=instance_dir,
|
||||
branch=data.branch or "main",
|
||||
)
|
||||
repo_path = clone_path
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to clone repository: %s", exc)
|
||||
cleanup_ssh_key_files(instance_dir)
|
||||
raise RuntimeError(f"Failed to clone repository: {exc}")
|
||||
else:
|
||||
repo_path = repo.path
|
||||
|
||||
# Verify cloned repo has files
|
||||
if data.clone_mode == "clone" and repo_path:
|
||||
try:
|
||||
repo_contents = os.listdir(repo_path)
|
||||
if not repo_contents or (
|
||||
len(repo_contents) == 1 and repo_contents[0] == ".git"
|
||||
):
|
||||
logger.error("Cloned repository at %s appears empty", repo_path)
|
||||
raise RuntimeError("Cloned repository is empty")
|
||||
logger.debug(
|
||||
"Verified cloned repo at %s has %d items",
|
||||
repo_path,
|
||||
len(repo_contents),
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to verify cloned repository: %s", exc)
|
||||
raise RuntimeError(f"Cloned repository verification failed: {exc}")
|
||||
|
||||
# Create new local branch if requested
|
||||
if data.clone_mode == "clone" and data.new_branch:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "-C", repo_path, "checkout", "-b", data.new_branch],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
logger.error(
|
||||
"Failed to create branch %s: %s", data.new_branch, result.stderr
|
||||
)
|
||||
raise RuntimeError(f"Failed to create branch: {result.stderr}")
|
||||
logger.debug(
|
||||
"Created local branch %s in cloned repository", data.new_branch
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to create local branch: %s", exc)
|
||||
raise RuntimeError(f"Failed to create local branch: {exc}")
|
||||
|
||||
# Handle based on definition type
|
||||
if tool_type.definition_type == "dockerfile":
|
||||
image_tag = f"headquarter/{instance_name}:latest".lower()
|
||||
@@ -1092,10 +1026,13 @@ async def create_tool_instance(
|
||||
|
||||
image_tag = compute_image_tag(tool_type.name, manifest)
|
||||
|
||||
# Manifest templates use WORKSPACE_PATH; REPO_PATH is retained as a
|
||||
# deprecated alias for backward compatibility with older templates.
|
||||
variables = {
|
||||
"IMAGE_TAG": image_tag,
|
||||
"INSTANCE_NAME": instance_name.lower(),
|
||||
"INSTANCE_DIR": instance_dir,
|
||||
"WORKSPACE_PATH": repo_path,
|
||||
"REPO_PATH": repo_path,
|
||||
"SSH_PATH": "",
|
||||
"TOOL_PORT": tool_port,
|
||||
@@ -1109,6 +1046,7 @@ async def create_tool_instance(
|
||||
if not tool_type.compose_template:
|
||||
raise ValueError("Tool type has no compose template configured")
|
||||
variables = {
|
||||
"WORKSPACE_PATH": repo_path,
|
||||
"REPO_PATH": repo_path,
|
||||
"INSTANCE_NAME": instance_name,
|
||||
"INSTANCE_ID": instance_name,
|
||||
@@ -1121,37 +1059,6 @@ async def create_tool_instance(
|
||||
tool_type.compose_template, variables
|
||||
)
|
||||
|
||||
if data.clone_mode == "clone" and repo_path:
|
||||
import yaml
|
||||
|
||||
compose_data = yaml.safe_load(compose_content)
|
||||
repo_mounted = False
|
||||
if compose_data and "services" in compose_data:
|
||||
for svc in compose_data["services"].values():
|
||||
volumes = svc.get("volumes", [])
|
||||
for vol in volumes:
|
||||
vol_str = str(vol)
|
||||
if repo_path in vol_str:
|
||||
repo_mounted = True
|
||||
break
|
||||
if repo_mounted:
|
||||
break
|
||||
|
||||
if not repo_mounted:
|
||||
logger.warning(
|
||||
"Compose template for tool type %s does not mount repo path; adding default mount",
|
||||
tool_type.name,
|
||||
)
|
||||
if compose_data and "services" in compose_data:
|
||||
for svc in compose_data["services"].values():
|
||||
if "volumes" not in svc:
|
||||
svc["volumes"] = []
|
||||
svc["volumes"].append(f"{repo_path}:/workspace")
|
||||
break
|
||||
compose_content = yaml.dump(
|
||||
compose_data, default_flow_style=False
|
||||
)
|
||||
|
||||
write_compose_file(instance_dir, compose_content)
|
||||
|
||||
# Create database record
|
||||
@@ -1166,10 +1073,8 @@ async def create_tool_instance(
|
||||
compose_path=compose_path,
|
||||
port=tool_port,
|
||||
workspace_id=workspace_id,
|
||||
clone_mode=data.clone_mode,
|
||||
branch=data.new_branch
|
||||
if data.new_branch
|
||||
else (data.branch if data.clone_mode == "clone" else None),
|
||||
clone_mode=None,
|
||||
branch=None,
|
||||
selected_config_profile_id=selected_profile_id,
|
||||
ssh_key_ids=data.ssh_key_ids or None,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user