v0.2.2: BUILD_FAILED pull failure aborts redeploy with clear message

Remove contextlib.suppress from the image pull step in the BUILD_FAILED
redeploy path. A failed pull (e.g. non-existent tag) now immediately
returns an actionable error pointing to update_image_tag instead of
silently continuing and starting the project with stale/missing image.

Also bumps version 0.2.1 → 0.2.2 and adds CHANGELOG entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 07:22:36 +02:00
parent 5cff7d8506
commit 7de4b56962
5 changed files with 43 additions and 5 deletions
+8
View File
@@ -2,6 +2,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.2.2] - 2026-04-21
### Fixed
- `redeploy_project` (BUILD_FAILED): Pull errors are no longer silently suppressed.
If the image pull fails (e.g. the tag in compose.yaml does not exist on the registry),
redeploy aborts immediately with a clear message pointing to `update_image_tag`.
## [0.2.1] - 2026-04-21 ## [0.2.1] - 2026-04-21
### Fixed ### Fixed
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "mcp-synology-container" name = "mcp-synology-container"
version = "0.2.1" version = "0.2.2"
description = "MCP server for Synology Container Manager" description = "MCP server for Synology Container Manager"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
@@ -167,8 +167,15 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
await client.request("SYNO.Docker.Project", "stop", params={"id": project_id}) await client.request("SYNO.Docker.Project", "stop", params={"id": project_id})
results.append(" Build stopped.") results.append(" Build stopped.")
results.append("Step 2/4: Pulling updated images...") results.append("Step 2/4: Pulling updated images...")
with contextlib.suppress(Exception): try:
await client.request("SYNO.Docker.Image", "pull", params={"id": project_id}) await client.request("SYNO.Docker.Image", "pull", params={"id": project_id})
except Exception as pull_err:
results.append(f" Pull failed: {pull_err}")
results.append(
"Aborted: image pull failed — the image tag in compose.yaml may not exist. "
"Fix the tag with update_image_tag, then retry redeploy_project."
)
return "\n".join(results)
results.append(" Images pulled.") results.append(" Images pulled.")
results.append("Step 3/4: Starting project...") results.append("Step 3/4: Starting project...")
await client.request("SYNO.Docker.Project", "start", params={"id": project_id}) await client.request("SYNO.Docker.Project", "start", params={"id": project_id})
+25 -2
View File
@@ -286,13 +286,13 @@ async def test_redeploy_build_failed_project():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_redeploy_build_failed_stop_error_nonfatal(): async def test_redeploy_build_failed_stop_error_nonfatal():
"""BUILD_FAILED: stop/pull failures must not abort the redeploy.""" """BUILD_FAILED: stop failure is non-fatal and must not abort the redeploy."""
from mcp_synology_container.dsm_client import SynologyError from mcp_synology_container.dsm_client import SynologyError
client, _ = make_stateful_redeploy_mock( client, _ = make_stateful_redeploy_mock(
"BUILD_FAILED", "BUILD_FAILED",
stop_raises=SynologyError("already stopped", code=2101), stop_raises=SynologyError("already stopped", code=2101),
pull_raises=SynologyError("pull failed", code=2102), pull_raises=None, # pull succeeds
) )
tools = make_projects_tools(client) tools = make_projects_tools(client)
@@ -302,6 +302,29 @@ async def test_redeploy_build_failed_stop_error_nonfatal():
assert "redeployed successfully" in result assert "redeployed successfully" in result
@pytest.mark.asyncio
async def test_redeploy_build_failed_pull_error_aborts():
"""BUILD_FAILED: pull failure must abort redeploy with a clear message."""
from mcp_synology_container.dsm_client import SynologyError
client, calls = make_stateful_redeploy_mock(
"BUILD_FAILED",
stop_raises=None,
pull_raises=SynologyError("image not found", code=114),
)
tools = make_projects_tools(client)
with patch("mcp_synology_container.modules.projects.asyncio.sleep"):
result = await tools["redeploy_project"]("myapp", confirmed=True)
assert "redeployed successfully" not in result
assert "Aborted" in result or "pull failed" in result.lower()
assert "compose.yaml" in result or "update_image_tag" in result
# start must NOT have been called after a pull failure
methods = [m for _, m in calls]
assert "start" not in methods
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_redeploy_poll_timeout(): async def test_redeploy_poll_timeout():
"""If project never reaches RUNNING after start, a warning is emitted.""" """If project never reaches RUNNING after start, a warning is emitted."""
Generated
+1 -1
View File
@@ -362,7 +362,7 @@ wheels = [
[[package]] [[package]]
name = "mcp-synology-container" name = "mcp-synology-container"
version = "0.2.1" version = "0.2.2"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },