fix: return the created template from appliance install

POST /appliances/{id}/install replied 204 with an empty body, so the MCP
appliance_install tool crashed with 'Expecting value: line 1 column 1'
while the template had actually been created. The route now returns the
created template (201, response_model=schemas.Template), _create_template
propagates it, and the MCP handler parses the body defensively so an
empty reply degrades to a plain success message.
This commit is contained in:
YueGuobin committed 2026-08-25 23:39:38 +08:00
1 parent 73e5e27c7b
commit 888afdccbd
6 files changed
+44 -12

No files matched your search

+1 -1
View File
@@ -1376,7 +1376,7 @@ async def appliance_install(
appliance_id: Annotated[str, Field(description="UUID of the appliance to install")],
version: Annotated[str | None, Field(description="Version to install (e.g. '2.7.0.356'). Required if the appliance has multiple versions. Use appliance_get to see available versions.")] = None,
) -> list[dict[str, Any]]:
"""Create a template from a GNS3 appliance definition.
"""Create a template from a GNS3 appliance definition and return the created template.
NOTE: This does NOT download images. Images must be placed in the
GNS3 images directory (e.g. ~/GNS3/images/) beforehand.
+10 -2
View File
@@ -85,5 +85,13 @@ def install_appliance_handler(params: dict[str, Any], gns3_ctx: dict[str, Any])
version = params.get("version")
if version:
request_params["version"] = version
result = conn.http_call("post", url, params=request_params).json()
return {"message": f"Appliance {appliance_id} installation requested", "result": result}
response = conn.http_call("post", url, params=request_params)
result = {"message": f"Appliance {appliance_id} installed"}
if response.content:
# the install endpoint returns the created template (201); tolerate an
# empty body in case an older server still replies with 204
template = response.json()
result["template"] = {
k: template[k] for k in ("template_id", "name", "version", "template_type") if k in template
}
return result
@@ -122,7 +122,8 @@ def add_appliance_version(appliance_id: UUID, appliance_version: Union[schemas.A
@router.post(
"/{appliance_id}/install",
status_code=status.HTTP_204_NO_CONTENT,
response_model=schemas.Template,
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(has_privilege("Appliance.Allocate"))]
)
async def install_appliance(
@@ -132,15 +133,15 @@ async def install_appliance(
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
current_user: schemas.User = Depends(get_current_active_user),
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
) -> None:
) -> schemas.Template:
"""
Install an appliance.
Install an appliance and return the created template.
Required privilege: Appliance.Allocate
"""
controller = Controller.instance()
await controller.appliance_manager.install_appliance(
return await controller.appliance_manager.install_appliance(
appliance_id,
version,
images_repo,
+3 -2
View File
@@ -204,9 +204,9 @@ class ApplianceManager:
else:
raise ControllerError(f"Could not find '{appliance_file}'")
async def _create_template(self, template_data, templates_repo, rbac_repo, current_user):
async def _create_template(self, template_data, templates_repo, rbac_repo, current_user) -> dict:
"""
Create a new template
Create a new template and return it as a dict.
"""
try:
@@ -217,6 +217,7 @@ class ApplianceManager:
#template_id = template.get("template_id")
#await rbac_repo.add_permission_to_user_with_path(current_user.user_id, f"/templates/{template_id}/*")
log.info(f"Template '{template.get('name')}' has been created")
return template
async def _appliance_to_template(self, appliance: Appliance, version: str = None) -> dict:
"""