From effea3fb26548e242b30fe9c8c3cbd20ef30a5e6 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Thu, 12 Mar 2026 23:03:19 +0800 Subject: [PATCH] feat(copilot): add Netmiko device list generator and simplify Huawei driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add script to generate Markdown documentation of all Netmiko supported devices (SSH and Telnet), grouped by platform with source attribution - Highlight custom GNS3-Copilot devices with "Custom ✨" marker - Auto-generate device list to docs/gns3-copilot/netmiko_devices.md - Simplify HuaweiTelnetCE driver: remove redundant aliases (huawei_ce, huawei_telnet_ce_telnet), keep only huawei_telnet_ce - Update tests to match simplified device registration --- docs/gns3-copilot/netmiko_devices.md | 234 ++++++++++ .../utils/custom_netmiko/huawei_ce.py | 9 +- .../scripts/list_netmiko_telnet_devices.py | 403 ++++++++++++++++++ .../custom_netmiko/tests/test_huawei_ce.py | 6 +- 4 files changed, 640 insertions(+), 12 deletions(-) create mode 100644 docs/gns3-copilot/netmiko_devices.md create mode 100644 gns3server/agent/gns3_copilot/utils/custom_netmiko/scripts/list_netmiko_telnet_devices.py diff --git a/docs/gns3-copilot/netmiko_devices.md b/docs/gns3-copilot/netmiko_devices.md new file mode 100644 index 000000000..4c053c62d --- /dev/null +++ b/docs/gns3-copilot/netmiko_devices.md @@ -0,0 +1,234 @@ +# Netmiko Supported Devices + +**Netmiko Version:** 4.6.0 +**Generated:** 2026-03-12 23:00:41 + +--- + +## Summary + +- **Total Device Types:** 364 +- **SSH Devices:** 154 +- **Telnet Devices:** 53 +- **Custom Devices:** 1 + +## SSH Supported Devices + +| Platform | Device Type | Source | +|----------|-------------|--------| +| A10 | `a10_ssh` | Netmiko | +| Accedian | `accedian_ssh` | Netmiko | +| Adtran | `adtran_os_ssh` | Netmiko | +| Adva | `adva_fsp150f2_ssh` | Netmiko | +| Adva | `adva_fsp150f3_ssh` | Netmiko | +| Alaxala | `alaxala_ax26s_ssh` | Netmiko | +| Alaxala | `alaxala_ax36s_ssh` | Netmiko | +| Alcatel | `alcatel_aos_ssh` | Netmiko | +| Alcatel | `alcatel_sros_ssh` | Netmiko | +| Allied | `allied_telesis_awplus_ssh` | Netmiko | +| Apresia | `apresia_aeos_ssh` | Netmiko | +| Arista | `arista_eos_ssh` | Netmiko | +| Arris | `arris_cer_ssh` | Netmiko | +| Aruba | `aruba_aoscx_ssh` | Netmiko | +| Aruba | `aruba_os_ssh` | Netmiko | +| Aruba | `aruba_osswitch_ssh` | Netmiko | +| Aruba | `aruba_procurve_ssh` | Netmiko | +| Asterfusion | `asterfusion_asternos_ssh` | Netmiko | +| Audiocode | `audiocode_66_ssh` | Netmiko | +| Audiocode | `audiocode_72_ssh` | Netmiko | +| Audiocode | `audiocode_shell_ssh` | Netmiko | +| Avaya | `avaya_ers_ssh` | Netmiko | +| Avaya | `avaya_vsp_ssh` | Netmiko | +| Bintec | `bintec_boss_ssh` | Netmiko | +| Broadcom | `broadcom_icos_ssh` | Netmiko | +| Brocade | `brocade_fastiron_ssh` | Netmiko | +| Brocade | `brocade_fos_ssh` | Netmiko | +| Brocade | `brocade_netiron_ssh` | Netmiko | +| Brocade | `brocade_nos_ssh` | Netmiko | +| Brocade | `brocade_vdx_ssh` | Netmiko | +| Brocade | `brocade_vyos_ssh` | Netmiko | +| Calix | `calix_b6_ssh` | Netmiko | +| Casa | `casa_cmts_ssh` | Netmiko | +| Cdot | `cdot_cros_ssh` | Netmiko | +| Centec | `centec_os_ssh` | Netmiko | +| Check Point | `checkpoint_gaia_ssh` | Netmiko | +| Ciena | `ciena_saos10_ssh` | Netmiko | +| Ciena | `ciena_saos_ssh` | Netmiko | +| Ciena | `ciena_waveserver_ssh` | Netmiko | +| Cisco | `cisco_apic_ssh` | Netmiko | +| Cisco | `cisco_asa_ssh` | Netmiko | +| Cisco | `cisco_ftd_ssh` | Netmiko | +| Cisco | `cisco_ios_ssh` | Netmiko | +| Cisco | `cisco_nxos_ssh` | Netmiko | +| Cisco | `cisco_s200_ssh` | Netmiko | +| Cisco | `cisco_s300_ssh` | Netmiko | +| Cisco | `cisco_tp_ssh` | Netmiko | +| Cisco | `cisco_viptela_ssh` | Netmiko | +| Cisco | `cisco_wlc_ssh` | Netmiko | +| Cisco | `cisco_xe_ssh` | Netmiko | +| Cisco | `cisco_xr_ssh` | Netmiko | +| Cloudgenix | `cloudgenix_ion_ssh` | Netmiko | +| Corelight | `corelight_linux_ssh` | Netmiko | +| Coriant | `coriant_ssh` | Netmiko | +| Cumulus | `cumulus_linux_ssh` | Netmiko | +| Dell | `dell_dnos9_ssh` | Netmiko | +| Dell | `dell_force10_ssh` | Netmiko | +| Dell | `dell_isilon_ssh` | Netmiko | +| Dell | `dell_os10_ssh` | Netmiko | +| Dell | `dell_os6_ssh` | Netmiko | +| Dell | `dell_os9_ssh` | Netmiko | +| Dell | `dell_powerconnect_ssh` | Netmiko | +| Dell | `dell_sonic_ssh` | Netmiko | +| Digi | `digi_transport_ssh` | Netmiko | +| Dlink | `dlink_ds_ssh` | Netmiko | +| Edgecore | `edgecore_sonic_ssh` | Netmiko | +| Ekinops | `ekinops_ek360_ssh` | Netmiko | +| Eltex | `eltex_esr_ssh` | Netmiko | +| Eltex | `eltex_ssh` | Netmiko | +| Endace | `endace_ssh` | Netmiko | +| Enterasys | `enterasys_ssh` | Netmiko | +| Ericsson | `ericsson_ipos_ssh` | Netmiko | +| Ericsson | `ericsson_mltn63_ssh` | Netmiko | +| Ericsson | `ericsson_mltn66_ssh` | Netmiko | +| Extreme | `extreme_ers_ssh` | Netmiko | +| Extreme | `extreme_exos_ssh` | Netmiko | +| Extreme | `extreme_netiron_ssh` | Netmiko | +| Extreme | `extreme_nos_ssh` | Netmiko | +| Extreme | `extreme_slx_ssh` | Netmiko | +| Extreme | `extreme_ssh` | Netmiko | +| Extreme | `extreme_tierra_ssh` | Netmiko | +| Extreme | `extreme_vdx_ssh` | Netmiko | +| Extreme | `extreme_vsp_ssh` | Netmiko | +| Extreme | `extreme_wing_ssh` | Netmiko | +| F5 | `f5_linux_ssh` | Netmiko | +| F5 | `f5_ltm_ssh` | Netmiko | +| F5 | `f5_tmsh_ssh` | Netmiko | +| Fiberstore | `fiberstore_fsos_ssh` | Netmiko | +| Fiberstore | `fiberstore_fsosv2_ssh` | Netmiko | +| Fiberstore | `fiberstore_networkos_ssh` | Netmiko | +| Flexvnf | `flexvnf_ssh` | Netmiko | +| Fortinet | `fortinet_ssh` | Netmiko | +| Garderos | `garderos_grs_ssh` | Netmiko | +| Generic | `generic_ssh` | Netmiko | +| Generic | `generic_termserver_ssh` | Netmiko | +| H3C (华三) | `h3c_comware_ssh` | Netmiko | +| HP | `hp_comware_ssh` | Netmiko | +| HP | `hp_procurve_ssh` | Netmiko | +| Hillstone | `hillstone_stoneos_ssh` | Netmiko | +| Huawei | `huawei_olt_ssh` | Netmiko | +| Huawei | `huawei_smartax_ssh` | Netmiko | +| Huawei | `huawei_smartaxmmi_ssh` | Netmiko | +| Huawei | `huawei_ssh` | Netmiko | +| Huawei | `huawei_vrp_ssh` | Netmiko | +| Huawei | `huawei_vrpv8_ssh` | Netmiko | +| Infinera | `infinera_packet_ssh` | Netmiko | +| Ipinfusion | `ipinfusion_ocnos_ssh` | Netmiko | +| Juniper | `juniper_junos_ssh` | Netmiko | +| Juniper | `juniper_screenos_ssh` | Netmiko | +| Juniper | `juniper_ssh` | Netmiko | +| Keymile | `keymile_nos_ssh` | Netmiko | +| Keymile | `keymile_ssh` | Netmiko | +| Lancom | `lancom_lcossx4_ssh` | Netmiko | +| Linux | `linux_ssh` | Netmiko | +| Maipu (迈普) | `maipu_ssh` | Netmiko | +| Mellanox | `mellanox_mlnxos_ssh` | Netmiko | +| Mellanox | `mellanox_ssh` | Netmiko | +| Mikrotik | `mikrotik_routeros_ssh` | Netmiko | +| Mikrotik | `mikrotik_switchos_ssh` | Netmiko | +| Mrv | `mrv_lx_ssh` | Netmiko | +| Mrv | `mrv_optiswitch_ssh` | Netmiko | +| Nec | `nec_ix_ssh` | Netmiko | +| Netapp | `netapp_cdot_ssh` | Netmiko | +| Netgear | `netgear_prosafe_ssh` | Netmiko | +| Netscaler | `netscaler_ssh` | Netmiko | +| Nokia | `nokia_srl_ssh` | Netmiko | +| Nokia | `nokia_sros_ssh` | Netmiko | +| Oneaccess | `oneaccess_oneos_ssh` | Netmiko | +| Ovs | `ovs_linux_ssh` | Netmiko | +| Palo Alto | `paloalto_panos_ssh` | Netmiko | +| Pluribus | `pluribus_ssh` | Netmiko | +| Quanta | `quanta_mesh_ssh` | Netmiko | +| Rad | `rad_etx_ssh` | Netmiko | +| Raisecom | `raisecom_roap_ssh` | Netmiko | +| Ruckus | `ruckus_fastiron_ssh` | Netmiko | +| Ruijie (锐捷) | `ruijie_os_ssh` | Netmiko | +| Silverpeak | `silverpeak_vxoa_ssh` | Netmiko | +| Sixwind | `sixwind_os_ssh` | Netmiko | +| Sophos | `sophos_sfos_ssh` | Netmiko | +| Supermicro | `supermicro_smis_ssh` | Netmiko | +| Telcosystems | `telcosystems_binos_ssh` | Netmiko | +| Teldat | `teldat_cit_ssh` | Netmiko | +| Tplink | `tplink_jetstream_ssh` | Netmiko | +| Ubiquiti | `ubiquiti_edge_ssh` | Netmiko | +| Ubiquiti | `ubiquiti_edgerouter_ssh` | Netmiko | +| Ubiquiti | `ubiquiti_edgeswitch_ssh` | Netmiko | +| Ubiquiti | `ubiquiti_unifiswitch_ssh` | Netmiko | +| Vertiv | `vertiv_mph_ssh` | Netmiko | +| Vyatta | `vyatta_vyos_ssh` | Netmiko | +| Vyos | `vyos_ssh` | Netmiko | +| Watchguard | `watchguard_fireware_ssh` | Netmiko | +| Yamaha | `yamaha_ssh` | Netmiko | +| ZTE (中兴) | `zte_zxros_ssh` | Netmiko | +| Zyxel | `zyxel_os_ssh` | Netmiko | + +## Telnet Supported Devices + +| Platform | Device Type | Source | +|----------|-------------|--------| +| Adtran | `adtran_os_telnet` | Netmiko | +| Apresia | `apresia_aeos_telnet` | Netmiko | +| Arista | `arista_eos_telnet` | Netmiko | +| Aruba | `aruba_procurve_telnet` | Netmiko | +| Audiocode | `audiocode_66_telnet` | Netmiko | +| Audiocode | `audiocode_72_telnet` | Netmiko | +| Audiocode | `audiocode_shell_telnet` | Netmiko | +| Bintec | `bintec_boss_telnet` | Netmiko | +| Brocade | `brocade_fastiron_telnet` | Netmiko | +| Brocade | `brocade_netiron_telnet` | Netmiko | +| Calix | `calix_b6_telnet` | Netmiko | +| Centec | `centec_os_telnet` | Netmiko | +| Ciena | `ciena_saos_telnet` | Netmiko | +| Cisco | `cisco_ios_telnet` | Netmiko | +| Cisco | `cisco_nxos_telnet` | Netmiko | +| Cisco | `cisco_s200_telnet` | Netmiko | +| Cisco | `cisco_s300_telnet` | Netmiko | +| Cisco | `cisco_xr_telnet` | Netmiko | +| Dell | `dell_dnos6_telnet` | Netmiko | +| Dell | `dell_powerconnect_telnet` | Netmiko | +| Dlink | `dlink_ds_telnet` | Netmiko | +| Extreme | `extreme_exos_telnet` | Netmiko | +| Extreme | `extreme_netiron_telnet` | Netmiko | +| Extreme | `extreme_telnet` | Netmiko | +| Fiberstore | `fiberstore_fsosv2_telnet` | Netmiko | +| Generic | `generic_telnet` | Netmiko | +| Generic | `generic_termserver_telnet` | Netmiko | +| Genexis | `genexis_solt33_telnet` | Netmiko | +| HP | `hp_comware_telnet` | Netmiko | +| HP | `hp_procurve_telnet` | Netmiko | +| Huawei | `huawei_olt_telnet` | Netmiko | +| Huawei | `huawei_telnet` | Netmiko | +| Huawei | `huawei_telnet_ce` | Custom ✨ | +| Infinera | `infinera_packet_telnet` | Netmiko | +| Ipinfusion | `ipinfusion_ocnos_telnet` | Netmiko | +| Juniper | `juniper_junos_telnet` | Netmiko | +| Maipu (迈普) | `maipu_telnet` | Netmiko | +| Nec | `nec_ix_telnet` | Netmiko | +| Nokia | `nokia_sros_telnet` | Netmiko | +| Oneaccess | `oneaccess_oneos_telnet` | Netmiko | +| Optilink | `optilink_eolt11444_telnet` | Netmiko | +| Optilink | `optilink_eolt9702_telnet` | Netmiko | +| Palo Alto | `paloalto_panos_telnet` | Netmiko | +| Rad | `rad_etx_telnet` | Netmiko | +| Raisecom | `raisecom_telnet` | Netmiko | +| Ruckus | `ruckus_fastiron_telnet` | Netmiko | +| Ruijie (锐捷) | `ruijie_os_telnet` | Netmiko | +| Supermicro | `supermicro_smis_telnet` | Netmiko | +| Telcosystems | `telcosystems_binos_telnet` | Netmiko | +| Teldat | `teldat_cit_telnet` | Netmiko | +| Tplink | `tplink_jetstream_telnet` | Netmiko | +| Yamaha | `yamaha_telnet` | Netmiko | +| ZTE (中兴) | `zte_zxros_telnet` | Netmiko | + +--- + +*This document was generated automatically by the Netmiko device list script.* \ No newline at end of file diff --git a/gns3server/agent/gns3_copilot/utils/custom_netmiko/huawei_ce.py b/gns3server/agent/gns3_copilot/utils/custom_netmiko/huawei_ce.py index ec387b50e..74d3bf6a4 100644 --- a/gns3server/agent/gns3_copilot/utils/custom_netmiko/huawei_ce.py +++ b/gns3server/agent/gns3_copilot/utils/custom_netmiko/huawei_ce.py @@ -396,19 +396,12 @@ def register_custom_device_type() -> None: # not a function sd = importlib.import_module("netmiko.ssh_dispatcher") - # Add to both mappers + # Register the device type in both mappers # CLASS_MAPPER_BASE is used for base class definitions sd.CLASS_MAPPER_BASE["huawei_telnet_ce"] = HuaweiTelnetCE - sd.CLASS_MAPPER_BASE["huawei_ce"] = HuaweiTelnetCE # CLASS_MAPPER is used by ConnectHandler for device type validation sd.CLASS_MAPPER["huawei_telnet_ce"] = HuaweiTelnetCE - sd.CLASS_MAPPER["huawei_ce"] = HuaweiTelnetCE - - # CRITICAL: Netmiko may also look for types with _telnet suffix - # Add this variant to ensure compatibility - sd.CLASS_MAPPER["huawei_telnet_ce_telnet"] = HuaweiTelnetCE - sd.CLASS_MAPPER_BASE["huawei_telnet_ce_telnet"] = HuaweiTelnetCE # CRITICAL: Update the static platforms lists # These lists are computed at module import time and won't diff --git a/gns3server/agent/gns3_copilot/utils/custom_netmiko/scripts/list_netmiko_telnet_devices.py b/gns3server/agent/gns3_copilot/utils/custom_netmiko/scripts/list_netmiko_telnet_devices.py new file mode 100644 index 000000000..b04d6c116 --- /dev/null +++ b/gns3server/agent/gns3_copilot/utils/custom_netmiko/scripts/list_netmiko_telnet_devices.py @@ -0,0 +1,403 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-3.0-or-later +# +# GNS3-Copilot - AI-powered Network Lab Assistant for GNS3 +# +# Script to list all Netmiko supported devices (SSH and Telnet) +# +# This script queries the Netmiko library to extract all device types +# that support SSH and Telnet connections, grouped by vendor/brand. +# Includes custom device drivers from the GNS3-Copilot project. +# Outputs a Markdown document with the complete device list. +# +# Usage: python list_netmiko_devices.py +# Output: netmiko_devices.md (in current directory) +# + +""" +Netmiko Device List Generator + +This script extracts and displays all SSH and Telnet-supported device +types from the Netmiko library, organized by vendor/brand. + +Features: +- Automatically detects Netmiko version +- Loads custom GNS3-Copilot device drivers +- Extracts all SSH and Telnet device types from CLASS_MAPPER +- Groups devices by vendor/brand +- Generates Markdown documentation +- Provides statistics and summary +""" + +import os +import sys +from typing import Dict, List, Tuple +from datetime import datetime + + +def get_netmiko_version() -> str: + """Get Netmiko library version.""" + try: + import netmiko + return netmiko.__version__ + except Exception: + return "Unknown" + + +def register_custom_drivers() -> set: + """ + Register custom GNS3-Copilot device drivers. + + This function imports and executes the registration functions for + custom device drivers, adding them to Netmiko's CLASS_MAPPER. + + Returns: + Set of custom device type names that were registered + """ + # Get the directory where this script is located + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # Find the custom drivers directory + custom_drivers_dir = os.path.dirname(script_dir) + + # Add to sys.path if not already there + if custom_drivers_dir not in sys.path: + sys.path.insert(0, custom_drivers_dir) + + custom_devices = set() + + try: + # Import and register Huawei CE driver + from gns3server.agent.gns3_copilot.utils.custom_netmiko \ + import huawei_ce + + # Store custom device types before registration + custom_devices.add("huawei_telnet_ce") + + # The driver auto-registers on import, but we can call it + # explicitly + if hasattr(huawei_ce, 'register_custom_device_type'): + huawei_ce.register_custom_device_type() + except ImportError: + # Silently skip if custom drivers are not available + pass + except Exception as e: + # Log but don't fail + print( + f"Warning: Failed to register custom drivers: {e}", + file=sys.stderr + ) + + return custom_devices + + +def extract_brand_name(device_type: str) -> str: + """ + Extract brand name from device type. + + Args: + device_type: Device type string (e.g., 'cisco_ios_telnet') + + Returns: + Brand name in a standardized format + """ + # Remove common suffixes + name = device_type.replace('_telnet', '').replace('_ssh', '') + name = name.replace('_serial', '') + + # Map to standard brand names + brand_mapping = { + 'cisco': 'Cisco', + 'huawei': 'Huawei', + 'juniper': 'Juniper', + 'arista': 'Arista', + 'hp': 'HP', + 'aruba': 'Aruba', + 'dell': 'Dell', + 'brocade': 'Brocade', + 'extreme': 'Extreme', + 'ruckus': 'Ruckus', + 'ruijie': 'Ruijie (锐捷)', + 'zte': 'ZTE (中兴)', + 'maipu': 'Maipu (迈普)', + 'h3c': 'H3C (华三)', + 'nokia': 'Nokia', + 'paloalto': 'Palo Alto', + 'f5': 'F5', + 'checkpoint': 'Check Point', + 'generic': 'Generic', + 'huaweiyt': 'HuaweiYT', + 'vsz': 'Ruckus', + } + + # Check for exact matches first + for key, value in brand_mapping.items(): + if name == key: + return value + + # Check for partial matches + for key, value in brand_mapping.items(): + if key in name: + return value + + # Return capitalized version of the first part + return name.split('_')[0].capitalize() + + +def group_devices_by_brand(device_types: List[str]) -> Dict[str, List[str]]: + """ + Group device types by brand. + + Args: + device_types: List of device type strings + + Returns: + Dictionary mapping brand names to device type lists + """ + brands = {} + + for device_type in device_types: + brand = extract_brand_name(device_type) + + if brand not in brands: + brands[brand] = [] + + brands[brand].append(device_type) + + return brands + + +def get_devices_by_protocol() -> Tuple[List[str], List[str]]: + """ + Get all SSH and Telnet device types from Netmiko. + + Returns: + Tuple of (ssh_devices, telnet_devices) as sorted lists + """ + try: + from netmiko.ssh_dispatcher import CLASS_MAPPER + + ssh_devices = [] + telnet_devices = [] + both_protocols = [] + + for device_type in CLASS_MAPPER.keys(): + device_lower = device_type.lower() + has_telnet = 'telnet' in device_lower + has_ssh = 'ssh' in device_lower + + if has_telnet and has_ssh: + both_protocols.append(device_type) + elif has_telnet: + telnet_devices.append(device_type) + elif has_ssh: + ssh_devices.append(device_type) + + # Devices with both protocols go to both lists + ssh_devices.extend(both_protocols) + telnet_devices.extend(both_protocols) + + return sorted(ssh_devices), sorted(telnet_devices) + + except ImportError as e: + print(f"Error: Failed to import Netmiko: {e}", file=sys.stderr) + sys.exit(1) + + +def get_total_device_count() -> int: + """Get total number of device types in Netmiko.""" + try: + from netmiko.ssh_dispatcher import CLASS_MAPPER + return len(CLASS_MAPPER) + except ImportError: + return 0 + + +def generate_markdown( + version: str, + ssh_devices: List[str], + telnet_devices: List[str], + total_count: int, + custom_devices: set +) -> str: + """ + Generate Markdown documentation with table format. + + Args: + version: Netmiko version string + ssh_devices: List of SSH device types + telnet_devices: List of Telnet device types + total_count: Total number of device types + custom_devices: Set of custom device type names + + Returns: + Markdown formatted string + """ + md_lines = [] + + # Header + md_lines.append("# Netmiko Supported Devices") + md_lines.append("") + md_lines.append(f"**Netmiko Version:** {version}") + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + md_lines.append(f"**Generated:** {timestamp}") + md_lines.append("") + md_lines.append("---") + md_lines.append("") + + # Summary + md_lines.append("## Summary") + md_lines.append("") + md_lines.append(f"- **Total Device Types:** {total_count}") + md_lines.append(f"- **SSH Devices:** {len(ssh_devices)}") + md_lines.append(f"- **Telnet Devices:** {len(telnet_devices)}") + md_lines.append(f"- **Custom Devices:** {len(custom_devices)}") + md_lines.append("") + + # SSH Devices Section + md_lines.append("## SSH Supported Devices") + md_lines.append("") + md_lines.append("| Platform | Device Type | Source |") + md_lines.append("|----------|-------------|--------|") + + ssh_brands = group_devices_by_brand(ssh_devices) + for brand in sorted(ssh_brands.keys()): + devices = sorted(ssh_brands[brand]) + for device in devices: + if device in custom_devices: + source = "Custom ✨" + else: + source = "Netmiko" + md_lines.append(f"| {brand} | `{device}` | {source} |") + md_lines.append("") + + # Telnet Devices Section + md_lines.append("## Telnet Supported Devices") + md_lines.append("") + md_lines.append("| Platform | Device Type | Source |") + md_lines.append("|----------|-------------|--------|") + + telnet_brands = group_devices_by_brand(telnet_devices) + for brand in sorted(telnet_brands.keys()): + devices = sorted(telnet_brands[brand]) + for device in devices: + if device in custom_devices: + source = "Custom ✨" + else: + source = "Netmiko" + md_lines.append(f"| {brand} | `{device}` | {source} |") + md_lines.append("") + + # Footer + md_lines.append("---") + md_lines.append("") + md_lines.append("*This document was generated automatically by the " + "Netmiko device list script.*") + + return "\n".join(md_lines) + + +def save_markdown(content: str, filename: str = "netmiko_devices.md") -> None: + """ + Save Markdown content to file in docs/gns3-copilot directory. + + Args: + content: Markdown content to save + filename: Output filename (default: netmiko_devices.md) + """ + # Get the project root directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + # Go up from: .../gns3server/agent/gns3_copilot/utils/ + # custom_netmiko/scripts to project root + # That's 6 levels up + project_root = os.path.dirname(os.path.dirname( + os.path.dirname(os.path.dirname( + os.path.dirname(os.path.dirname(script_dir)))) + ) + ) + + # Target directory: docs/gns3-copilot + target_dir = os.path.join(project_root, "docs", "gns3-copilot") + + # Create directory if it doesn't exist + os.makedirs(target_dir, exist_ok=True) + + # Full file path + output_path = os.path.join(target_dir, filename) + + try: + with open(output_path, 'w', encoding='utf-8') as f: + f.write(content) + print(f"Markdown document saved to: {output_path}") + except IOError as e: + print(f"Error: Failed to save Markdown file: {e}", + file=sys.stderr) + sys.exit(1) + + +def print_summary(version: str, ssh_count: int, telnet_count: int, + total_count: int, output_file: str) -> None: + """Print summary to console.""" + print("=" * 80) + print("Netmiko Device List Generator") + print(f"Netmiko Version: {version}") + print("=" * 80) + print() + print(f"Total SSH devices: {ssh_count}") + print(f"Total Telnet devices: {telnet_count}") + print(f"Total device types: {total_count}") + print() + print(f"Output file: {output_file}") + print("=" * 80) + + +def main() -> int: + """ + Main function to execute the script. + + Returns: + Exit code (0 for success, 1 for error) + """ + # Register custom GNS3-Copilot drivers + custom_devices = register_custom_drivers() + + # Get Netmiko version + version = get_netmiko_version() + + # Get SSH and Telnet devices + ssh_devices, telnet_devices = get_devices_by_protocol() + + if not ssh_devices and not telnet_devices: + print("Error: No devices found!", file=sys.stderr) + return 1 + + # Get total device count + total_count = get_total_device_count() + + # Generate Markdown + markdown_content = generate_markdown( + version=version, + ssh_devices=ssh_devices, + telnet_devices=telnet_devices, + total_count=total_count, + custom_devices=custom_devices + ) + + # Save to file + output_file = "netmiko_devices.md" + save_markdown(markdown_content, output_file) + + # Print summary + print_summary( + version=version, + ssh_count=len(ssh_devices), + telnet_count=len(telnet_devices), + total_count=total_count, + output_file=output_file + ) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_huawei_ce.py b/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_huawei_ce.py index d940cb848..4c459527e 100644 --- a/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_huawei_ce.py +++ b/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_huawei_ce.py @@ -51,10 +51,8 @@ class TestHuaweiTelnetCEDriver(unittest.TestCase): # Check CLASS_MAPPER_BASE self.assertIn("huawei_telnet_ce", CLASS_MAPPER_BASE) - self.assertEqual(CLASS_MAPPER_BASE["huawei_telnet_ce"], self.HuaweiTelnetCE) - - # Check alias - self.assertIn("huawei_ce", CLASS_MAPPER) + self.assertEqual(CLASS_MAPPER_BASE["huawei_telnet_ce"], + self.HuaweiTelnetCE) def test_inheritance_from_huawei_base(self): """Test that HuaweiTelnetCE inherits from HuaweiBase."""