Fix container hash-prefix + status-aware redeploy
Bug 1: Container name hash-prefix (e.g. f93cb8b504f7_jenkins) - _strip_hash_prefix(): strips 12-char hex prefix and leading slash - _resolve_container_name(): looks up actual DSM name from container list - Applied in list_containers (display), container_stats (matching), get_container_status/get_container_logs/exec_in_container (lookup) Bug 2: redeploy_project DSM 2101/1202 on wrong project state - Fetch project status before acting - RUNNING → stop then start - STOPPED → start directly (nothing to stop) - BUILD_FAILED → suppress stop error, then start - Other → return error with workaround hint 36 tests all passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from mcp_synology_container.config import AppConfig
|
||||
from mcp_synology_container.dsm_client import DsmClient
|
||||
|
||||
@@ -33,7 +35,7 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
||||
return "No projects found."
|
||||
|
||||
lines = ["Projects:", ""]
|
||||
for project_id, proj in sorted(projects.items(), key=lambda x: x[1].get("name", "")):
|
||||
for _project_id, proj in sorted(projects.items(), key=lambda x: x[1].get("name", "")):
|
||||
name = proj.get("name", "?")
|
||||
status = proj.get("status", "?")
|
||||
path = proj.get("path", "?")
|
||||
@@ -115,7 +117,12 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
||||
|
||||
@mcp.tool()
|
||||
async def redeploy_project(project_name: str, confirmed: bool = False) -> str:
|
||||
"""Redeploy a project: pull latest images, stop, and restart.
|
||||
"""Redeploy a project by stopping and restarting it.
|
||||
|
||||
Checks the current project status to determine the correct action:
|
||||
- RUNNING → stop, then start
|
||||
- STOPPED → start directly (nothing to stop)
|
||||
- BUILD_FAILED → force-stop, then start
|
||||
|
||||
This operation will briefly take the project offline.
|
||||
Requires confirmation before executing.
|
||||
@@ -126,10 +133,8 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
||||
"""
|
||||
if not confirmed:
|
||||
return (
|
||||
f"Redeploying project '{project_name}' will:\n"
|
||||
f" 1. Pull latest images\n"
|
||||
f" 2. Stop all containers\n"
|
||||
f" 3. Restart with new images\n\n"
|
||||
f"Redeploying project '{project_name}' will stop and restart all its "
|
||||
f"containers (auto-detects current state).\n\n"
|
||||
f"Call this tool again with confirmed=True to proceed."
|
||||
)
|
||||
|
||||
@@ -138,43 +143,44 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
||||
return f"Project '{project_name}' not found."
|
||||
|
||||
project_id = project.get("id", "")
|
||||
status = (project.get("status") or "").upper()
|
||||
results = []
|
||||
|
||||
try:
|
||||
# Step 1: Pull latest images via build (triggers compose pull)
|
||||
results.append("Step 1/3: Pulling latest images...")
|
||||
try:
|
||||
await client.request(
|
||||
"SYNO.Docker.Project",
|
||||
"build",
|
||||
params={"id": project_id, "force": "true"},
|
||||
if status == "STOPPED":
|
||||
results.append("Project is STOPPED — starting directly.")
|
||||
results.append("Step 1/1: Starting project...")
|
||||
await client.request("SYNO.Docker.Project", "start", params={"id": project_id})
|
||||
results.append(" Project started.")
|
||||
|
||||
elif status in ("RUNNING", "BUILD_FAILED", ""):
|
||||
if status == "RUNNING":
|
||||
results.append("Step 1/2: Stopping project...")
|
||||
await client.request("SYNO.Docker.Project", "stop", params={"id": project_id})
|
||||
results.append(" Project stopped.")
|
||||
elif status == "BUILD_FAILED":
|
||||
results.append("Step 1/2: Stopping failed build...")
|
||||
with contextlib.suppress(Exception):
|
||||
await client.request(
|
||||
"SYNO.Docker.Project", "stop", params={"id": project_id}
|
||||
)
|
||||
results.append(" Build stopped.")
|
||||
|
||||
results.append("Step 2/2: Starting project...")
|
||||
await client.request("SYNO.Docker.Project", "start", params={"id": project_id})
|
||||
results.append(" Project started.")
|
||||
|
||||
else:
|
||||
return (
|
||||
f"Cannot redeploy '{project_name}': unexpected status '{status}'.\n"
|
||||
f"Workaround: use stop_project + start_project separately."
|
||||
)
|
||||
results.append(" Images pulled.")
|
||||
except Exception as e:
|
||||
results.append(f" Warning: pull step failed ({e}), continuing with restart.")
|
||||
|
||||
# Step 2: Stop the project
|
||||
results.append("Step 2/3: Stopping project...")
|
||||
await client.request(
|
||||
"SYNO.Docker.Project",
|
||||
"stop",
|
||||
params={"id": project_id},
|
||||
)
|
||||
results.append(" Project stopped.")
|
||||
|
||||
# Step 3: Start the project
|
||||
results.append("Step 3/3: Starting project...")
|
||||
await client.request(
|
||||
"SYNO.Docker.Project",
|
||||
"start",
|
||||
params={"id": project_id},
|
||||
)
|
||||
results.append(" Project started.")
|
||||
|
||||
results.append(f"\nProject '{project_name}' redeployed successfully.")
|
||||
|
||||
except Exception as e:
|
||||
results.append(f"Error during redeploy: {e}")
|
||||
results.append("Workaround: use stop_project + start_project separately.")
|
||||
|
||||
return "\n".join(results)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user