netbox-toolkit-plugin 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netbox_toolkit_plugin/__init__.py +32 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/serializers.py +71 -35
- {netbox_toolkit → netbox_toolkit_plugin}/api/urls.py +3 -3
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/factory.py +170 -111
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/netmiko_connector.py +242 -179
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/scrapli_connector.py +256 -172
- netbox_toolkit_plugin/migrations/0001_initial.py +108 -0
- netbox_toolkit_plugin/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py +70 -0
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0003_permission_system_update.py +26 -12
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0004_remove_django_permissions.py +27 -29
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0005_alter_command_options_and_more.py +7 -8
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0006_commandlog_parsed_data_commandlog_parsing_success_and_more.py +7 -8
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/0007_alter_commandlog_parsing_template.py +6 -4
- {netbox_toolkit → netbox_toolkit_plugin}/models.py +31 -32
- {netbox_toolkit → netbox_toolkit_plugin}/navigation.py +6 -6
- {netbox_toolkit → netbox_toolkit_plugin}/services/command_service.py +188 -128
- {netbox_toolkit → netbox_toolkit_plugin}/services/rate_limiting_service.py +104 -97
- netbox_toolkit_plugin/settings.py +176 -0
- netbox_toolkit_plugin/tables.py +51 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/command.html +108 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/command_list.html +12 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/commandlog.html +170 -0
- netbox_toolkit_plugin/templates/netbox_toolkit/device_toolkit.html +557 -0
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command.html +5 -5
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command_list.html +2 -2
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/commandlog.html +2 -2
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/device_toolkit.html +6 -6
- netbox_toolkit_plugin/urls.py +38 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/logging.py +20 -19
- {netbox_toolkit → netbox_toolkit_plugin}/views.py +251 -169
- {netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.2.dist-info}/METADATA +2 -2
- netbox_toolkit_plugin-0.1.2.dist-info/RECORD +60 -0
- netbox_toolkit_plugin-0.1.2.dist-info/entry_points.txt +2 -0
- netbox_toolkit_plugin-0.1.2.dist-info/top_level.txt +1 -0
- netbox_toolkit/__init__.py +0 -30
- netbox_toolkit/config.py +0 -159
- netbox_toolkit/migrations/0001_initial.py +0 -54
- netbox_toolkit/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py +0 -66
- netbox_toolkit/tables.py +0 -37
- netbox_toolkit/urls.py +0 -22
- netbox_toolkit_plugin-0.1.0.dist-info/RECORD +0 -56
- netbox_toolkit_plugin-0.1.0.dist-info/entry_points.txt +0 -2
- netbox_toolkit_plugin-0.1.0.dist-info/top_level.txt +0 -1
- {netbox_toolkit → netbox_toolkit_plugin}/admin.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/mixins.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/schemas.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/views/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/views/command_logs.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/api/views/commands.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/connectors/base.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/exceptions.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/filtersets.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/forms.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/migrations/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/search.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/services/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/services/device_service.py +0 -0
- {netbox_toolkit/static/netbox_toolkit → netbox_toolkit_plugin/static/netbox_toolkit_plugin}/css/toolkit.css +0 -0
- {netbox_toolkit/static/netbox_toolkit → netbox_toolkit_plugin/static/netbox_toolkit_plugin}/js/toolkit.js +0 -0
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command_edit.html +0 -0
- {netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/commandlog_list.html +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/__init__.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/connection.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/error_parser.py +0 -0
- {netbox_toolkit → netbox_toolkit_plugin}/utils/network.py +0 -0
- {netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.2.dist-info}/WHEEL +0 -0
- {netbox_toolkit_plugin-0.1.0.dist-info → netbox_toolkit_plugin-0.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Factory for creating device connectors."""
|
2
|
+
|
2
3
|
import logging
|
3
4
|
from typing import Type, Optional
|
4
5
|
|
@@ -6,7 +7,7 @@ from django.db.models import Model
|
|
6
7
|
from dcim.models import Device
|
7
8
|
|
8
9
|
from ..exceptions import UnsupportedPlatformError, DeviceConnectionError
|
9
|
-
from ..
|
10
|
+
from ..settings import ToolkitSettings
|
10
11
|
from ..utils.logging import get_toolkit_logger
|
11
12
|
from .base import BaseDeviceConnector, ConnectionConfig
|
12
13
|
from .scrapli_connector import ScrapliConnector
|
@@ -17,43 +18,41 @@ logger = get_toolkit_logger(__name__)
|
|
17
18
|
|
18
19
|
class ConnectorFactory:
|
19
20
|
"""Factory for creating device connectors with Scrapli primary and Netmiko fallback."""
|
20
|
-
|
21
|
+
|
21
22
|
# Primary connector (high-performance)
|
22
23
|
PRIMARY_CONNECTOR = ScrapliConnector
|
23
|
-
|
24
|
+
|
24
25
|
# Fallback connector (legacy device support)
|
25
26
|
FALLBACK_CONNECTOR = NetmikoConnector
|
26
|
-
|
27
|
+
|
27
28
|
# Platform-specific connector mappings
|
28
29
|
CONNECTOR_MAP = {
|
29
30
|
# Scrapli-optimized platforms (use primary connector)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
31
|
+
"cisco_ios": ScrapliConnector,
|
32
|
+
"cisco_nxos": ScrapliConnector,
|
33
|
+
"cisco_iosxr": ScrapliConnector,
|
34
|
+
"cisco_xe": ScrapliConnector,
|
35
|
+
"ios": ScrapliConnector,
|
36
|
+
"nxos": ScrapliConnector,
|
37
|
+
"iosxr": ScrapliConnector,
|
38
|
+
"ios-xe": ScrapliConnector,
|
39
|
+
"ios-xr": ScrapliConnector,
|
40
40
|
# Platforms that may work better with Netmiko fallback
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
"juniper_junos": ScrapliConnector, # Try Scrapli first
|
42
|
+
"arista_eos": ScrapliConnector, # Try Scrapli first
|
43
|
+
"linux": ScrapliConnector, # Try Scrapli first
|
45
44
|
# Legacy/specialized platforms - direct to Netmiko
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
"hp_procurve": NetmikoConnector,
|
46
|
+
"hp_comware": NetmikoConnector,
|
47
|
+
"dell_os10": NetmikoConnector,
|
48
|
+
"dell_powerconnect": NetmikoConnector,
|
49
|
+
"cisco_asa": NetmikoConnector,
|
50
|
+
"paloalto_panos": NetmikoConnector,
|
51
|
+
"fortinet": NetmikoConnector,
|
52
|
+
"mikrotik_routeros": NetmikoConnector,
|
53
|
+
"ubiquiti_edge": NetmikoConnector,
|
55
54
|
}
|
56
|
-
|
55
|
+
|
57
56
|
@classmethod
|
58
57
|
def create_connector(
|
59
58
|
cls,
|
@@ -61,88 +60,121 @@ class ConnectorFactory:
|
|
61
60
|
username: str,
|
62
61
|
password: str,
|
63
62
|
connector_type: Optional[str] = None,
|
64
|
-
use_fallback: bool = True
|
63
|
+
use_fallback: bool = True,
|
65
64
|
) -> BaseDeviceConnector:
|
66
65
|
"""
|
67
66
|
Create a device connector with Scrapli primary and Netmiko fallback strategy.
|
68
|
-
|
67
|
+
|
69
68
|
Args:
|
70
69
|
device: NetBox Device instance
|
71
70
|
username: Authentication username
|
72
71
|
password: Authentication password
|
73
72
|
connector_type: Override connector type (optional)
|
74
73
|
use_fallback: Whether to attempt fallback to Netmiko on Scrapli failure
|
75
|
-
|
74
|
+
|
76
75
|
Returns:
|
77
76
|
Device connector instance
|
78
|
-
|
77
|
+
|
79
78
|
Raises:
|
80
79
|
UnsupportedPlatformError: If platform is not supported by any connector
|
81
80
|
DeviceConnectionError: If both primary and fallback connectors fail
|
82
81
|
"""
|
83
|
-
logger.debug(
|
84
|
-
|
85
|
-
|
82
|
+
logger.debug(
|
83
|
+
"Creating connector for device %s (platform: %s)",
|
84
|
+
device.name,
|
85
|
+
device.platform,
|
86
|
+
)
|
87
|
+
|
86
88
|
config = cls._build_connection_config(device, username, password)
|
87
|
-
|
89
|
+
|
88
90
|
# Determine connector class
|
89
91
|
if connector_type:
|
90
92
|
logger.debug("Using override connector type: %s", connector_type)
|
91
93
|
connector_class = cls._get_connector_by_type(connector_type)
|
92
|
-
logger.info(
|
93
|
-
|
94
|
+
logger.info(
|
95
|
+
"Created %s connector for device %s",
|
96
|
+
connector_class.__name__,
|
97
|
+
device.name,
|
98
|
+
)
|
94
99
|
return connector_class(config)
|
95
|
-
|
100
|
+
|
96
101
|
# Try platform-specific connector first
|
97
|
-
primary_connector_class = cls._get_primary_connector_by_platform(
|
98
|
-
|
102
|
+
primary_connector_class = cls._get_primary_connector_by_platform(
|
103
|
+
config.platform
|
104
|
+
)
|
105
|
+
|
99
106
|
try:
|
100
|
-
logger.debug(
|
101
|
-
|
107
|
+
logger.debug(
|
108
|
+
"Attempting primary connector: %s", primary_connector_class.__name__
|
109
|
+
)
|
110
|
+
|
102
111
|
# Create a clean config for the primary connector
|
103
|
-
primary_config = cls._prepare_connector_config(
|
112
|
+
primary_config = cls._prepare_connector_config(
|
113
|
+
config, primary_connector_class
|
114
|
+
)
|
104
115
|
connector = primary_connector_class(primary_config)
|
105
|
-
|
116
|
+
|
106
117
|
# For ScrapliConnector, rely on fast-fail logic in connect() method
|
107
118
|
# instead of pre-testing connection. This avoids double connection attempts.
|
108
|
-
logger.info(
|
109
|
-
|
119
|
+
logger.info(
|
120
|
+
"Created %s connector for device %s",
|
121
|
+
primary_connector_class.__name__,
|
122
|
+
device.name,
|
123
|
+
)
|
110
124
|
return connector
|
111
|
-
|
125
|
+
|
112
126
|
except Exception as e:
|
113
127
|
# Check if this is a fast-fail scenario for immediate Netmiko fallback
|
114
128
|
error_msg = str(e)
|
115
|
-
if (
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
129
|
+
if (
|
130
|
+
use_fallback
|
131
|
+
and primary_connector_class == ScrapliConnector
|
132
|
+
and (
|
133
|
+
"Fast-fail to Netmiko" in error_msg
|
134
|
+
or ToolkitSettings.should_fast_fail_to_netmiko(error_msg)
|
135
|
+
)
|
136
|
+
):
|
137
|
+
logger.info(
|
138
|
+
"Fast-fail pattern detected during connector creation for %s",
|
139
|
+
device.name,
|
140
|
+
)
|
120
141
|
return cls._create_fallback_connector(config, device.name, error_msg)
|
121
142
|
elif use_fallback and primary_connector_class != NetmikoConnector:
|
122
|
-
logger.warning(
|
143
|
+
logger.warning(
|
144
|
+
"Primary connector failed for %s: %s", device.name, error_msg
|
145
|
+
)
|
123
146
|
return cls._create_fallback_connector(config, device.name, error_msg)
|
124
147
|
else:
|
125
148
|
raise DeviceConnectionError(f"Connector creation failed: {error_msg}")
|
126
|
-
|
149
|
+
|
127
150
|
@classmethod
|
128
|
-
def _create_fallback_connector(
|
151
|
+
def _create_fallback_connector(
|
152
|
+
cls, config: ConnectionConfig, device_name: str, primary_error: str
|
153
|
+
) -> BaseDeviceConnector:
|
129
154
|
"""Create fallback Netmiko connector when primary fails."""
|
130
155
|
logger.info("Falling back to Netmiko connector for device %s", device_name)
|
131
156
|
try:
|
132
157
|
# Create a clean config specifically for Netmiko
|
133
158
|
fallback_config = cls._prepare_connector_config(config, NetmikoConnector)
|
134
159
|
connector = NetmikoConnector(fallback_config)
|
135
|
-
logger.info(
|
160
|
+
logger.info(
|
161
|
+
"Successfully created Netmiko fallback connector for device %s",
|
162
|
+
device_name,
|
163
|
+
)
|
136
164
|
return connector
|
137
165
|
except Exception as fallback_error:
|
138
|
-
logger.error(
|
166
|
+
logger.error(
|
167
|
+
"Both primary and fallback connectors failed for %s", device_name
|
168
|
+
)
|
139
169
|
raise DeviceConnectionError(
|
140
170
|
f"Both connectors failed. Primary error: {primary_error}. "
|
141
171
|
f"Fallback error: {str(fallback_error)}"
|
142
172
|
)
|
143
|
-
|
173
|
+
|
144
174
|
@classmethod
|
145
|
-
def _prepare_connector_config(
|
175
|
+
def _prepare_connector_config(
|
176
|
+
cls, base_config: ConnectionConfig, connector_class: Type[BaseDeviceConnector]
|
177
|
+
) -> ConnectionConfig:
|
146
178
|
"""Prepare a clean configuration for a specific connector type."""
|
147
179
|
# Create a copy of the base config
|
148
180
|
config = ConnectionConfig(
|
@@ -156,139 +188,166 @@ class ConnectorFactory:
|
|
156
188
|
auth_strict_key=base_config.auth_strict_key,
|
157
189
|
transport=base_config.transport,
|
158
190
|
platform=base_config.platform,
|
159
|
-
extra_options=None # Start with clean extra_options
|
191
|
+
extra_options=None, # Start with clean extra_options
|
160
192
|
)
|
161
|
-
|
193
|
+
|
162
194
|
# Add connector-specific configurations
|
163
195
|
if connector_class == NetmikoConnector:
|
164
196
|
# For Netmiko, add Netmiko-specific config to extra_options
|
165
|
-
netmiko_config =
|
197
|
+
netmiko_config = ToolkitSettings.get_netmiko_config()
|
166
198
|
config.extra_options = netmiko_config.copy()
|
167
|
-
logger.debug(
|
168
|
-
|
199
|
+
logger.debug(
|
200
|
+
"Prepared Netmiko config with options: %s", list(netmiko_config.keys())
|
201
|
+
)
|
202
|
+
|
169
203
|
elif connector_class == ScrapliConnector:
|
170
204
|
# For Scrapli, keep extra_options clean (transport options are handled separately)
|
171
205
|
# Any Scrapli-specific config would go here
|
172
206
|
config.extra_options = None
|
173
207
|
logger.debug("Prepared clean Scrapli config")
|
174
|
-
|
208
|
+
|
175
209
|
return config
|
176
|
-
|
210
|
+
|
177
211
|
@classmethod
|
178
|
-
def _build_connection_config(
|
212
|
+
def _build_connection_config(
|
213
|
+
cls, device: Device, username: str, password: str
|
214
|
+
) -> ConnectionConfig:
|
179
215
|
"""Build connection configuration from device properties."""
|
180
216
|
logger.debug("Building connection config for device %s", device.name)
|
181
|
-
|
217
|
+
|
182
218
|
# Get device connection details
|
183
|
-
hostname =
|
219
|
+
hostname = (
|
220
|
+
str(device.primary_ip.address.ip) if device.primary_ip else device.name
|
221
|
+
)
|
184
222
|
platform = str(device.platform).lower() if device.platform else None
|
185
|
-
|
186
|
-
logger.debug(
|
187
|
-
|
223
|
+
|
224
|
+
logger.debug(
|
225
|
+
"Connection details - hostname: %s, platform: %s", hostname, platform
|
226
|
+
)
|
227
|
+
|
188
228
|
# Normalize platform using config
|
189
|
-
normalized_platform =
|
229
|
+
normalized_platform = ToolkitSettings.normalize_platform(platform)
|
190
230
|
logger.debug("Normalized platform: %s -> %s", platform, normalized_platform)
|
191
|
-
|
231
|
+
|
192
232
|
# Get timeouts based on device type
|
193
233
|
device_model = str(device.device_type.model) if device.device_type else None
|
194
|
-
timeouts =
|
195
|
-
|
234
|
+
timeouts = ToolkitSettings.get_timeouts_for_device(device_model)
|
235
|
+
|
196
236
|
# Build configuration
|
197
237
|
config = ConnectionConfig(
|
198
238
|
hostname=hostname,
|
199
239
|
username=username,
|
200
240
|
password=password,
|
201
241
|
platform=normalized_platform,
|
202
|
-
timeout_socket=timeouts[
|
203
|
-
timeout_transport=timeouts[
|
204
|
-
timeout_ops=timeouts[
|
242
|
+
timeout_socket=timeouts["socket"],
|
243
|
+
timeout_transport=timeouts["transport"],
|
244
|
+
timeout_ops=timeouts["ops"],
|
205
245
|
)
|
206
|
-
|
246
|
+
|
207
247
|
# Add device-specific customizations if needed
|
208
248
|
config = cls._customize_config_for_device(config, device)
|
209
|
-
|
249
|
+
|
210
250
|
return config
|
211
|
-
|
251
|
+
|
212
252
|
@classmethod
|
213
|
-
def _customize_config_for_device(
|
253
|
+
def _customize_config_for_device(
|
254
|
+
cls, config: ConnectionConfig, device: Device
|
255
|
+
) -> ConnectionConfig:
|
214
256
|
"""Customize configuration based on device properties."""
|
215
257
|
# Add custom port if specified in device custom fields
|
216
258
|
# (This would require custom fields to be defined in NetBox)
|
217
259
|
# if hasattr(device, 'cf') and device.cf.get('ssh_port'):
|
218
260
|
# config.port = int(device.cf['ssh_port'])
|
219
|
-
|
261
|
+
|
220
262
|
return config
|
221
|
-
|
263
|
+
|
222
264
|
@classmethod
|
223
265
|
def _get_connector_by_type(cls, connector_type: str) -> Type[BaseDeviceConnector]:
|
224
266
|
"""Get connector class by explicit type."""
|
225
267
|
connector_type_lower = connector_type.lower()
|
226
|
-
if connector_type_lower ==
|
268
|
+
if connector_type_lower == "scrapli":
|
227
269
|
return ScrapliConnector
|
228
|
-
elif connector_type_lower ==
|
270
|
+
elif connector_type_lower == "netmiko":
|
229
271
|
return NetmikoConnector
|
230
272
|
else:
|
231
|
-
raise UnsupportedPlatformError(
|
232
|
-
|
273
|
+
raise UnsupportedPlatformError(
|
274
|
+
f"Unsupported connector type: {connector_type}"
|
275
|
+
)
|
276
|
+
|
233
277
|
@classmethod
|
234
|
-
def _get_primary_connector_by_platform(
|
278
|
+
def _get_primary_connector_by_platform(
|
279
|
+
cls, platform: Optional[str]
|
280
|
+
) -> Type[BaseDeviceConnector]:
|
235
281
|
"""Get primary connector class by device platform."""
|
236
282
|
if not platform:
|
237
283
|
return cls.PRIMARY_CONNECTOR
|
238
|
-
|
284
|
+
|
239
285
|
platform_lower = platform.lower()
|
240
|
-
|
286
|
+
|
241
287
|
# Check for exact match first
|
242
288
|
if platform_lower in cls.CONNECTOR_MAP:
|
243
289
|
return cls.CONNECTOR_MAP[platform_lower]
|
244
|
-
|
290
|
+
|
245
291
|
# Check for partial matches (e.g., 'ios' in 'cisco_ios')
|
246
292
|
for supported_platform, connector_class in cls.CONNECTOR_MAP.items():
|
247
|
-
if
|
293
|
+
if (
|
294
|
+
platform_lower in supported_platform
|
295
|
+
or supported_platform in platform_lower
|
296
|
+
):
|
248
297
|
return connector_class
|
249
|
-
|
298
|
+
|
250
299
|
# If no specific match, use primary connector
|
251
300
|
return cls.PRIMARY_CONNECTOR
|
252
|
-
|
301
|
+
|
253
302
|
@classmethod
|
254
|
-
def _get_connector_by_platform(
|
303
|
+
def _get_connector_by_platform(
|
304
|
+
cls, platform: Optional[str]
|
305
|
+
) -> Type[BaseDeviceConnector]:
|
255
306
|
"""Legacy method for backward compatibility."""
|
256
307
|
return cls._get_primary_connector_by_platform(platform)
|
257
|
-
|
308
|
+
|
258
309
|
@classmethod
|
259
310
|
def get_supported_platforms(cls) -> list[str]:
|
260
311
|
"""Get list of supported platforms from both connectors."""
|
261
312
|
scrapli_platforms = ScrapliConnector.get_supported_platforms()
|
262
313
|
netmiko_platforms = NetmikoConnector.get_supported_platforms()
|
263
|
-
|
314
|
+
|
264
315
|
# Combine and deduplicate
|
265
|
-
all_platforms = list(
|
316
|
+
all_platforms = list(
|
317
|
+
set(scrapli_platforms + netmiko_platforms + list(cls.CONNECTOR_MAP.keys()))
|
318
|
+
)
|
266
319
|
return sorted(all_platforms)
|
267
|
-
|
320
|
+
|
268
321
|
@classmethod
|
269
322
|
def is_platform_supported(cls, platform: str) -> bool:
|
270
323
|
"""Check if a platform is supported by any connector."""
|
271
324
|
if not platform:
|
272
325
|
return True # Default connectors can handle unknown platforms
|
273
|
-
|
326
|
+
|
274
327
|
platform_lower = platform.lower()
|
275
|
-
|
328
|
+
|
276
329
|
# Check exact match in our mapping
|
277
330
|
if platform_lower in cls.CONNECTOR_MAP:
|
278
331
|
return True
|
279
|
-
|
332
|
+
|
280
333
|
# Check partial matches
|
281
334
|
for supported_platform in cls.CONNECTOR_MAP.keys():
|
282
|
-
if
|
335
|
+
if (
|
336
|
+
platform_lower in supported_platform
|
337
|
+
or supported_platform in platform_lower
|
338
|
+
):
|
283
339
|
return True
|
284
|
-
|
340
|
+
|
285
341
|
# Check if either connector supports it
|
286
|
-
if
|
287
|
-
|
342
|
+
if platform_lower in [
|
343
|
+
p.lower() for p in ScrapliConnector.get_supported_platforms()
|
344
|
+
] or platform_lower in [
|
345
|
+
p.lower() for p in NetmikoConnector.get_supported_platforms()
|
346
|
+
]:
|
288
347
|
return True
|
289
|
-
|
348
|
+
|
290
349
|
return False
|
291
|
-
|
350
|
+
|
292
351
|
@classmethod
|
293
352
|
def get_recommended_connector(cls, platform: Optional[str]) -> str:
|
294
353
|
"""Get recommended connector type for a platform."""
|