From 1e7bd0a540d6bc8b8adc5894f2608957acdab63a Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Thu, 28 May 2026 21:53:17 +0200 Subject: [PATCH] fix: handle manifest definition type in create_instance create_instance had an if/else where the else branch handled both compose and manifest types. For manifest types, compose_template is NULL (migrated tools no longer store raw compose strings), so render_compose_template(None,...) crashed with 'NoneType' object has no attribute 'replace'. Add an explicit elif tool_type.definition_type == 'manifest' branch that: 1. Looks up the ToolDefinitionManifest from tool_type.manifest_id 2. Resolves base definition if referenced 3. Computes deterministic image tag 4. Generates compose via compile_compose Legacy compose types continue to use render_compose_template in the else branch. --- apps/api/src/api/tool_instances.py | 40 +++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/apps/api/src/api/tool_instances.py b/apps/api/src/api/tool_instances.py index 109dfa3..bf661f9 100644 --- a/apps/api/src/api/tool_instances.py +++ b/apps/api/src/api/tool_instances.py @@ -747,8 +747,46 @@ services: """ write_compose_file(instance_dir, compose_content) + elif tool_type.definition_type == "manifest": + # Manifest-based: generate compose from manifest definition + from src.models.tool_definition_manifest import ToolDefinitionManifest + + manifest_def = await session.get( + ToolDefinitionManifest, tool_type.manifest_id + ) + if not manifest_def: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Manifest definition not found for this tool type", + ) + + manifest = dict(manifest_def.manifest) + if manifest_def.base_definition_id: + base_def = await session.get( + ToolDefinitionManifest, manifest_def.base_definition_id + ) + if base_def: + manifest = resolve_base( + deep_merge(dict(base_def.manifest), manifest) + ) + + image_tag = compute_image_tag(tool_type.name, manifest) + + variables = { + "IMAGE_TAG": image_tag, + "INSTANCE_NAME": instance_name.lower(), + "INSTANCE_DIR": instance_dir, + "REPO_PATH": repo_path, + "SSH_PATH": "", + "TOOL_PORT": tool_port, + "EXTRA_ENV": {}, + "EXTRA_VOLUMES": [], + } + compose_content = compile_compose(manifest, variables) + write_compose_file(instance_dir, compose_content) + else: - # Render compose template + # Render compose template (legacy) variables = { "REPO_PATH": repo_path, "INSTANCE_NAME": instance_name,