feat: workspace backend integration (PR-2)
- Add workspace_id to CreateInstanceRequest (optional, replaces clone_mode) - create_instance: resolve workspace, validate repo ownership, use workspace.path - create_instance: store workspace_id on ToolInstance record - start_instance: use workspace.path when workspace_id is set (manifest + legacy flows) - Skip SSH key mount for clone mode when workspace is used - Backward compatible: clone_mode still works when workspace_id is absent
This commit is contained in:
@@ -427,6 +427,9 @@ class CreateInstanceRequest(BaseModel):
|
|||||||
display_name: str | None = Field(
|
display_name: str | None = Field(
|
||||||
default=None, description="Optional display name for the instance"
|
default=None, description="Optional display name for the instance"
|
||||||
)
|
)
|
||||||
|
workspace_id: str | None = Field(
|
||||||
|
default=None, description="UUID of workspace to mount (replaces clone_mode)"
|
||||||
|
)
|
||||||
clone_mode: str = Field(
|
clone_mode: str = Field(
|
||||||
default="mount", description="Repository access mode: 'mount' or 'clone'"
|
default="mount", description="Repository access mode: 'mount' or 'clone'"
|
||||||
)
|
)
|
||||||
@@ -849,9 +852,34 @@ async def create_instance(
|
|||||||
session, data.config_profile_id, user_id, project_id, tool_type_id
|
session, data.config_profile_id, user_id, project_id, tool_type_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Resolve workspace if provided
|
||||||
|
workspace = None
|
||||||
|
workspace_id = None
|
||||||
|
if data.workspace_id:
|
||||||
|
from src.models.workspace import Workspace as WorkspaceModel
|
||||||
|
|
||||||
|
try:
|
||||||
|
workspace_id = uuid.UUID(data.workspace_id)
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Invalid workspace_id format",
|
||||||
|
)
|
||||||
|
workspace = await session.get(WorkspaceModel, workspace_id)
|
||||||
|
if workspace is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="workspace not found",
|
||||||
|
)
|
||||||
|
if workspace.repo_id != repo_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="workspace does not belong to this repository",
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Validate clone mode requirements
|
# Validate clone mode requirements (legacy path)
|
||||||
if data.clone_mode == "clone":
|
if data.clone_mode == "clone" and not workspace:
|
||||||
if not repo.remote_url:
|
if not repo.remote_url:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
@@ -876,8 +904,10 @@ async def create_instance(
|
|||||||
# Find free port
|
# Find free port
|
||||||
tool_port = find_free_port()
|
tool_port = find_free_port()
|
||||||
|
|
||||||
# Determine repo path based on clone mode
|
# Determine repo path based on workspace or clone mode
|
||||||
if data.clone_mode == "clone":
|
if workspace:
|
||||||
|
repo_path = workspace.path
|
||||||
|
elif data.clone_mode == "clone":
|
||||||
# Get SSH key for cloning
|
# Get SSH key for cloning
|
||||||
ssh_key = await session.get(SSHKey, repo.ssh_key_id)
|
ssh_key = await session.get(SSHKey, repo.ssh_key_id)
|
||||||
if ssh_key is None:
|
if ssh_key is None:
|
||||||
@@ -1143,6 +1173,7 @@ services:
|
|||||||
status="pending",
|
status="pending",
|
||||||
compose_path=compose_path,
|
compose_path=compose_path,
|
||||||
port=tool_port,
|
port=tool_port,
|
||||||
|
workspace_id=workspace_id,
|
||||||
clone_mode=data.clone_mode,
|
clone_mode=data.clone_mode,
|
||||||
branch=data.new_branch
|
branch=data.new_branch
|
||||||
if data.new_branch
|
if data.new_branch
|
||||||
@@ -1649,11 +1680,18 @@ async def start_instance(
|
|||||||
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:
|
||||||
logger.info("Using manifest-based startup for instance %s", instance.id)
|
logger.info("Using manifest-based startup for instance %s", instance.id)
|
||||||
|
|
||||||
# Determine repo path
|
# Determine repo path (workspace takes precedence)
|
||||||
repo = await session.get(GitRepository, instance.repository_id)
|
repo_path = ""
|
||||||
repo_path = repo.path if repo else ""
|
if instance.workspace_id:
|
||||||
if instance.clone_mode == "clone":
|
from src.models.workspace import Workspace as WorkspaceModel
|
||||||
repo_path = os.path.join(instance_dir, "repo-clone")
|
workspace = await session.get(WorkspaceModel, instance.workspace_id)
|
||||||
|
if workspace:
|
||||||
|
repo_path = workspace.path
|
||||||
|
else:
|
||||||
|
repo = await session.get(GitRepository, instance.repository_id)
|
||||||
|
repo_path = repo.path if repo else ""
|
||||||
|
if instance.clone_mode == "clone":
|
||||||
|
repo_path = os.path.join(instance_dir, "repo-clone")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(
|
(
|
||||||
@@ -1686,8 +1724,8 @@ async def start_instance(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# ── LEGACY FLOW ──────────────────────────────────────────
|
# ── LEGACY FLOW ──────────────────────────────────────────
|
||||||
# Mount SSH key for clone-mode instances
|
# Mount SSH key for clone-mode instances (skip for workspace-based)
|
||||||
if instance.clone_mode == "clone":
|
if instance.clone_mode == "clone" and not instance.workspace_id:
|
||||||
repo = await session.get(GitRepository, instance.repository_id)
|
repo = await session.get(GitRepository, instance.repository_id)
|
||||||
if repo and repo.ssh_key_id:
|
if repo and repo.ssh_key_id:
|
||||||
ssh_key = await session.get(SSHKey, repo.ssh_key_id)
|
ssh_key = await session.get(SSHKey, repo.ssh_key_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user