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.
This commit is contained in:
YueGuobin 2026-08-16 12:39:47 +08:00
parent 254721dbda
commit 9260108f53
No known key found for this signature in database
4 changed files with 37 additions and 2 deletions

View File

@ -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)

View File

@ -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')

View File

@ -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')"

View File

@ -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: