From 9260108f533b2f0a391cdbf24cde0aee67e61e92 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sun, 16 Aug 2026 12:39:47 +0800 Subject: [PATCH] templates: add netmiko_device_type for automation tools Common template field (schema + templates table column + Alembic migration) holding the Netmiko device type (e.g. 'cisco_xr', 'nokia_srl') so Netmiko/Nornir based tooling can look up how to reach a node's CLI without hard-coded vendor mappings. Free-form lowercase string on purpose: Netmiko's platform list evolves independently of GNS3. --- gns3server/db/models/templates.py | 1 + ...4f_add_netmiko_device_type_to_templates.py | 26 +++++++++++++++++++ .../schemas/controller/templates/__init__.py | 5 ++++ tests/api/routes/controller/test_templates.py | 7 +++-- 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 gns3server/db_migrations/versions/b3c7e2a91d4f_add_netmiko_device_type_to_templates.py diff --git a/gns3server/db/models/templates.py b/gns3server/db/models/templates.py index 5c271e436..6afb4f7cd 100644 --- a/gns3server/db/models/templates.py +++ b/gns3server/db/models/templates.py @@ -35,6 +35,7 @@ class Template(BaseTable): symbol = Column(String) builtin = Column(Boolean, default=False) usage = Column(String) + netmiko_device_type = Column(String) template_type = Column(String) tags = Column(JSON) compute_id = Column(String) diff --git a/gns3server/db_migrations/versions/b3c7e2a91d4f_add_netmiko_device_type_to_templates.py b/gns3server/db_migrations/versions/b3c7e2a91d4f_add_netmiko_device_type_to_templates.py new file mode 100644 index 000000000..bc0241c12 --- /dev/null +++ b/gns3server/db_migrations/versions/b3c7e2a91d4f_add_netmiko_device_type_to_templates.py @@ -0,0 +1,26 @@ +"""add netmiko_device_type to templates table + +Revision ID: b3c7e2a91d4f +Revises: 8f2a1c4e9d3b +Create Date: 2026-08-16 10:00:00.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b3c7e2a91d4f' +down_revision = '8f2a1c4e9d3b' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + + op.add_column('templates', sa.Column('netmiko_device_type', sa.String())) + + +def downgrade() -> None: + + op.drop_column('templates', 'netmiko_device_type') diff --git a/gns3server/schemas/controller/templates/__init__.py b/gns3server/schemas/controller/templates/__init__.py index dee74b7b9..2cd3b53c6 100644 --- a/gns3server/schemas/controller/templates/__init__.py +++ b/gns3server/schemas/controller/templates/__init__.py @@ -48,6 +48,11 @@ class TemplateBase(BaseModel): template_type: Optional[NodeType] = None compute_id: Optional[str] = None usage: Optional[str] = "" + netmiko_device_type: Optional[str] = Field( + None, + description="Device type for Netmiko-based automation tools (e.g. 'cisco_xr' or 'nokia_srl')", + pattern=r"^[a-z0-9_]+$", + ) tags: Optional[List[str]] = Field( default_factory=list, description="User-defined metadata tags (e.g. 'vendor:cisco' or 'model:7200')" diff --git a/tests/api/routes/controller/test_templates.py b/tests/api/routes/controller/test_templates.py index cd1745783..773be1459 100644 --- a/tests/api/routes/controller/test_templates.py +++ b/tests/api/routes/controller/test_templates.py @@ -146,7 +146,8 @@ class TestTemplateRoutes: "version": "3.0", "compute_id": "local", "template_type": "vpcs", - "tags": ["tag1", "tag2"] + "tags": ["tag1", "tag2"], + "netmiko_device_type": "generic_termserver_telnet" } response = await client.post(app.url_path_for("create_template"), json=params) @@ -156,12 +157,14 @@ class TestTemplateRoutes: assert response.status_code == status.HTTP_200_OK assert response.json()["template_id"] == template_id assert response.json()["tags"] == ["tag1", "tag2"] + assert response.json()["netmiko_device_type"] == "generic_termserver_telnet" - params = {"name": "VPCS_TEST_RENAMED", "console_auto_start": True} + params = {"name": "VPCS_TEST_RENAMED", "console_auto_start": True, "netmiko_device_type": "cisco_ios"} response = await client.put(app.url_path_for("update_template", template_id=template_id), json=params) assert response.status_code == status.HTTP_200_OK assert response.json()["name"] == "VPCS_TEST_RENAMED" + assert response.json()["netmiko_device_type"] == "cisco_ios" async def test_template_delete(self, app: FastAPI, client: AsyncClient) -> None: