netbox-toolkit-plugin 0.1.0__tar.gz → 0.1.1__tar.gz
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-0.1.0/netbox_toolkit_plugin.egg-info → netbox_toolkit_plugin-0.1.1}/PKG-INFO +2 -2
- {netbox_toolkit_plugin-0.1.0 → netbox_toolkit_plugin-0.1.1}/README.md +1 -1
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/__init__.py +32 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/api/serializers.py +71 -35
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/api/urls.py +3 -3
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/config.py +80 -73
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/connectors/factory.py +170 -111
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/connectors/netmiko_connector.py +242 -179
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/connectors/scrapli_connector.py +256 -172
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/migrations/0001_initial.py +108 -0
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py +70 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/migrations/0003_permission_system_update.py +26 -12
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/migrations/0004_remove_django_permissions.py +27 -29
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/migrations/0005_alter_command_options_and_more.py +7 -8
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/migrations/0006_commandlog_parsed_data_commandlog_parsing_success_and_more.py +7 -8
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/migrations/0007_alter_commandlog_parsing_template.py +6 -4
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/models.py +31 -32
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/navigation.py +6 -6
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/services/command_service.py +188 -128
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/services/rate_limiting_service.py +104 -97
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/tables.py +51 -0
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit/command.html +108 -0
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit/command_list.html +12 -0
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit/commandlog.html +170 -0
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit/device_toolkit.html +557 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command.html +5 -5
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command_list.html +2 -2
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/commandlog.html +2 -2
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/device_toolkit.html +6 -6
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/urls.py +38 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/utils/logging.py +20 -19
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/views.py +251 -169
- {netbox_toolkit_plugin-0.1.0 → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin.egg-info}/PKG-INFO +2 -2
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin.egg-info/SOURCES.txt +63 -0
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin.egg-info/entry_points.txt +2 -0
- netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin.egg-info/top_level.txt +1 -0
- {netbox_toolkit_plugin-0.1.0 → netbox_toolkit_plugin-0.1.1}/pyproject.toml +3 -3
- netbox_toolkit_plugin-0.1.0/netbox_toolkit/__init__.py +0 -30
- netbox_toolkit_plugin-0.1.0/netbox_toolkit/migrations/0001_initial.py +0 -54
- netbox_toolkit_plugin-0.1.0/netbox_toolkit/migrations/0002_alter_command_options_alter_command_unique_together_and_more.py +0 -66
- netbox_toolkit_plugin-0.1.0/netbox_toolkit/tables.py +0 -37
- netbox_toolkit_plugin-0.1.0/netbox_toolkit/urls.py +0 -22
- netbox_toolkit_plugin-0.1.0/netbox_toolkit_plugin.egg-info/SOURCES.txt +0 -59
- netbox_toolkit_plugin-0.1.0/netbox_toolkit_plugin.egg-info/entry_points.txt +0 -2
- netbox_toolkit_plugin-0.1.0/netbox_toolkit_plugin.egg-info/top_level.txt +0 -1
- {netbox_toolkit_plugin-0.1.0 → netbox_toolkit_plugin-0.1.1}/LICENSE +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/admin.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/api/__init__.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/api/mixins.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/api/schemas.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/api/views/__init__.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/api/views/command_logs.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/api/views/commands.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/connectors/__init__.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/connectors/base.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/exceptions.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/filtersets.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/forms.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/migrations/__init__.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/search.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/services/__init__.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/services/device_service.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit/static/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/static/netbox_toolkit_plugin}/css/toolkit.css +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit/static/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/static/netbox_toolkit_plugin}/js/toolkit.js +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/command_edit.html +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit/templates/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin/templates/netbox_toolkit_plugin}/commandlog_list.html +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/utils/__init__.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/utils/connection.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/utils/error_parser.py +0 -0
- {netbox_toolkit_plugin-0.1.0/netbox_toolkit → netbox_toolkit_plugin-0.1.1/netbox_toolkit_plugin}/utils/network.py +0 -0
- {netbox_toolkit_plugin-0.1.0 → netbox_toolkit_plugin-0.1.1}/netbox_toolkit_plugin.egg-info/dependency_links.txt +0 -0
- {netbox_toolkit_plugin-0.1.0 → netbox_toolkit_plugin-0.1.1}/netbox_toolkit_plugin.egg-info/requires.txt +0 -0
- {netbox_toolkit_plugin-0.1.0 → netbox_toolkit_plugin-0.1.1}/setup.cfg +0 -0
{netbox_toolkit_plugin-0.1.0/netbox_toolkit_plugin.egg-info → netbox_toolkit_plugin-0.1.1}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: netbox-toolkit-plugin
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.1
|
4
4
|
Summary: NetBox plugin for running pre-defined commands on network devices
|
5
5
|
Author: Andy Norwood
|
6
6
|
Classifier: Development Status :: 3 - Alpha
|
@@ -58,7 +58,7 @@ A NetBox plugin that allows you to run commands on network devices directly from
|
|
58
58
|
|
59
59
|
#### 📋 Command Management
|
60
60
|
- [📋 Command Creation](./docs/user/command-creation.md) - Create platform-specific commands
|
61
|
-
- [🔐 Permissions Setup](./docs/user/
|
61
|
+
- [🔐 Permissions Setup](./docs/user/permissions-setup-guide.md) - Configure granular access control
|
62
62
|
- [📝 Permission Examples](./docs/user/permission-examples.md) - Example permission configuration
|
63
63
|
|
64
64
|
#### 🔧 Troubleshooting
|
@@ -37,7 +37,7 @@ A NetBox plugin that allows you to run commands on network devices directly from
|
|
37
37
|
|
38
38
|
#### 📋 Command Management
|
39
39
|
- [📋 Command Creation](./docs/user/command-creation.md) - Create platform-specific commands
|
40
|
-
- [🔐 Permissions Setup](./docs/user/
|
40
|
+
- [🔐 Permissions Setup](./docs/user/permissions-setup-guide.md) - Configure granular access control
|
41
41
|
- [📝 Permission Examples](./docs/user/permission-examples.md) - Example permission configuration
|
42
42
|
|
43
43
|
#### 🔧 Troubleshooting
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from netbox.plugins import PluginConfig
|
2
|
+
|
3
|
+
__author__ = "Andy Norwood"
|
4
|
+
__version__ = "0.1.1"
|
5
|
+
|
6
|
+
|
7
|
+
class ToolkitPluginConfig(PluginConfig):
|
8
|
+
"""NetBox plugin configuration for the Toolkit plugin."""
|
9
|
+
|
10
|
+
name = "netbox_toolkit_plugin"
|
11
|
+
verbose_name = "Command Toolkit Plugin"
|
12
|
+
description = "NetBox plugin for running pre-defined commands on network devices"
|
13
|
+
version = __version__
|
14
|
+
author = __author__
|
15
|
+
base_url = "toolkit"
|
16
|
+
min_version = "4.2.0"
|
17
|
+
|
18
|
+
# Database migrations
|
19
|
+
required_settings = []
|
20
|
+
|
21
|
+
# Default plugin settings
|
22
|
+
default_settings = {
|
23
|
+
"rate_limiting_enabled": True,
|
24
|
+
"device_command_limit": 10,
|
25
|
+
"time_window_minutes": 5,
|
26
|
+
"bypass_users": [],
|
27
|
+
"bypass_groups": [],
|
28
|
+
"debug_logging": False, # Enable debug logging for this plugin
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
config = ToolkitPluginConfig
|
@@ -3,33 +3,36 @@ from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializ
|
|
3
3
|
from dcim.api.serializers import PlatformSerializer, DeviceSerializer
|
4
4
|
from ..models import Command, CommandLog
|
5
5
|
|
6
|
+
|
6
7
|
class CommandExecutionSerializer(serializers.Serializer):
|
7
8
|
"""Serializer for command execution input validation"""
|
9
|
+
|
8
10
|
device_id = serializers.IntegerField(
|
9
11
|
help_text="ID of the device to execute the command on"
|
10
12
|
)
|
11
13
|
username = serializers.CharField(
|
12
14
|
max_length=100,
|
13
15
|
help_text="Username for device authentication",
|
14
|
-
trim_whitespace=True
|
16
|
+
trim_whitespace=True,
|
15
17
|
)
|
16
18
|
password = serializers.CharField(
|
17
19
|
max_length=255,
|
18
|
-
style={
|
20
|
+
style={"input_type": "password"},
|
19
21
|
help_text="Password for device authentication",
|
20
|
-
trim_whitespace=False
|
22
|
+
trim_whitespace=False,
|
21
23
|
)
|
22
24
|
timeout = serializers.IntegerField(
|
23
25
|
required=False,
|
24
26
|
default=30,
|
25
27
|
min_value=5,
|
26
28
|
max_value=300,
|
27
|
-
help_text="Command execution timeout in seconds (5-300)"
|
29
|
+
help_text="Command execution timeout in seconds (5-300)",
|
28
30
|
)
|
29
|
-
|
31
|
+
|
30
32
|
def validate_device_id(self, value):
|
31
33
|
"""Validate that the device exists and has required attributes"""
|
32
34
|
from dcim.models import Device
|
35
|
+
|
33
36
|
try:
|
34
37
|
device = Device.objects.get(id=value)
|
35
38
|
if not device.platform:
|
@@ -43,17 +46,17 @@ class CommandExecutionSerializer(serializers.Serializer):
|
|
43
46
|
return value
|
44
47
|
except Device.DoesNotExist:
|
45
48
|
raise serializers.ValidationError("Device not found")
|
46
|
-
|
49
|
+
|
47
50
|
def validate(self, data):
|
48
51
|
"""Cross-field validation and object retrieval"""
|
49
52
|
from dcim.models import Device
|
50
|
-
|
53
|
+
|
51
54
|
# Get the actual device object for use in views
|
52
|
-
device = Device.objects.get(id=data[
|
53
|
-
data[
|
54
|
-
|
55
|
+
device = Device.objects.get(id=data["device_id"])
|
56
|
+
data["device"] = device
|
57
|
+
|
55
58
|
return data
|
56
|
-
|
59
|
+
|
57
60
|
def validate_username(self, value):
|
58
61
|
"""Validate username format"""
|
59
62
|
if not value.strip():
|
@@ -61,7 +64,7 @@ class CommandExecutionSerializer(serializers.Serializer):
|
|
61
64
|
if len(value.strip()) < 2:
|
62
65
|
raise serializers.ValidationError("Username must be at least 2 characters")
|
63
66
|
return value.strip()
|
64
|
-
|
67
|
+
|
65
68
|
def validate_password(self, value):
|
66
69
|
"""Validate password"""
|
67
70
|
if not value:
|
@@ -70,33 +73,45 @@ class CommandExecutionSerializer(serializers.Serializer):
|
|
70
73
|
raise serializers.ValidationError("Password must be at least 3 characters")
|
71
74
|
return value
|
72
75
|
|
76
|
+
|
73
77
|
class NestedCommandSerializer(WritableNestedSerializer):
|
74
78
|
url = serializers.HyperlinkedIdentityField(
|
75
|
-
view_name=
|
79
|
+
view_name="plugins-api:netbox_toolkit_plugin-api:command-detail"
|
76
80
|
)
|
77
81
|
|
78
82
|
class Meta:
|
79
83
|
model = Command
|
80
|
-
fields = (
|
84
|
+
fields = ("id", "url", "name", "display")
|
85
|
+
|
81
86
|
|
82
87
|
class CommandSerializer(NetBoxModelSerializer):
|
83
88
|
url = serializers.HyperlinkedIdentityField(
|
84
|
-
view_name=
|
89
|
+
view_name="plugins-api:netbox_toolkit_plugin-api:command-detail"
|
85
90
|
)
|
86
91
|
platform = PlatformSerializer(nested=True)
|
87
92
|
|
88
93
|
class Meta:
|
89
94
|
model = Command
|
90
95
|
fields = (
|
91
|
-
|
92
|
-
|
93
|
-
|
96
|
+
"id",
|
97
|
+
"url",
|
98
|
+
"display",
|
99
|
+
"name",
|
100
|
+
"command",
|
101
|
+
"description",
|
102
|
+
"platform",
|
103
|
+
"command_type",
|
104
|
+
"tags",
|
105
|
+
"custom_fields",
|
106
|
+
"created",
|
107
|
+
"last_updated",
|
94
108
|
)
|
95
|
-
brief_fields = (
|
109
|
+
brief_fields = ("id", "url", "display", "name", "command_type", "platform")
|
110
|
+
|
96
111
|
|
97
112
|
class CommandLogSerializer(NetBoxModelSerializer):
|
98
113
|
url = serializers.HyperlinkedIdentityField(
|
99
|
-
view_name=
|
114
|
+
view_name="plugins-api:netbox_toolkit_plugin-api:commandlog-detail"
|
100
115
|
)
|
101
116
|
command = NestedCommandSerializer()
|
102
117
|
device = DeviceSerializer(nested=True)
|
@@ -104,38 +119,58 @@ class CommandLogSerializer(NetBoxModelSerializer):
|
|
104
119
|
class Meta:
|
105
120
|
model = CommandLog
|
106
121
|
fields = (
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
122
|
+
"id",
|
123
|
+
"url",
|
124
|
+
"display",
|
125
|
+
"command",
|
126
|
+
"device",
|
127
|
+
"output",
|
128
|
+
"username",
|
129
|
+
"execution_time",
|
130
|
+
"success",
|
131
|
+
"error_message",
|
132
|
+
"execution_duration",
|
133
|
+
"parsed_data",
|
134
|
+
"parsing_success",
|
135
|
+
"parsing_template",
|
136
|
+
"created",
|
137
|
+
"last_updated",
|
138
|
+
)
|
139
|
+
brief_fields = (
|
140
|
+
"id",
|
141
|
+
"url",
|
142
|
+
"display",
|
143
|
+
"command",
|
144
|
+
"device",
|
145
|
+
"username",
|
146
|
+
"execution_time",
|
147
|
+
"success",
|
111
148
|
)
|
112
|
-
|
149
|
+
|
113
150
|
|
114
151
|
class BulkCommandExecutionSerializer(serializers.Serializer):
|
115
152
|
"""Serializer for bulk command execution validation"""
|
116
|
-
|
117
|
-
|
118
|
-
)
|
153
|
+
|
154
|
+
command_id = serializers.IntegerField(help_text="ID of the command to execute")
|
119
155
|
device_id = serializers.IntegerField(
|
120
156
|
help_text="ID of the device to execute the command on"
|
121
157
|
)
|
122
158
|
username = serializers.CharField(
|
123
|
-
max_length=100,
|
124
|
-
help_text="Username for device authentication"
|
159
|
+
max_length=100, help_text="Username for device authentication"
|
125
160
|
)
|
126
161
|
password = serializers.CharField(
|
127
162
|
max_length=255,
|
128
|
-
style={
|
129
|
-
help_text="Password for device authentication"
|
163
|
+
style={"input_type": "password"},
|
164
|
+
help_text="Password for device authentication",
|
130
165
|
)
|
131
166
|
timeout = serializers.IntegerField(
|
132
167
|
required=False,
|
133
168
|
default=30,
|
134
169
|
min_value=5,
|
135
170
|
max_value=300,
|
136
|
-
help_text="Command execution timeout in seconds"
|
171
|
+
help_text="Command execution timeout in seconds",
|
137
172
|
)
|
138
|
-
|
173
|
+
|
139
174
|
def validate_command_id(self, value):
|
140
175
|
"""Validate that the command exists"""
|
141
176
|
try:
|
@@ -143,10 +178,11 @@ class BulkCommandExecutionSerializer(serializers.Serializer):
|
|
143
178
|
return value
|
144
179
|
except Command.DoesNotExist:
|
145
180
|
raise serializers.ValidationError("Command not found")
|
146
|
-
|
181
|
+
|
147
182
|
def validate_device_id(self, value):
|
148
183
|
"""Validate that the device exists"""
|
149
184
|
from dcim.models import Device
|
185
|
+
|
150
186
|
try:
|
151
187
|
device = Device.objects.get(id=value)
|
152
188
|
if not device.platform:
|
@@ -1,10 +1,10 @@
|
|
1
1
|
from netbox.api.routers import NetBoxRouter
|
2
2
|
from .views import CommandViewSet, CommandLogViewSet
|
3
3
|
|
4
|
-
app_name =
|
4
|
+
app_name = "netbox_toolkit_plugin"
|
5
5
|
|
6
6
|
router = NetBoxRouter()
|
7
|
-
router.register(
|
8
|
-
router.register(
|
7
|
+
router.register("commands", CommandViewSet)
|
8
|
+
router.register("command-logs", CommandLogViewSet)
|
9
9
|
|
10
10
|
urlpatterns = router.urls
|
@@ -1,81 +1,82 @@
|
|
1
1
|
"""Configuration settings for the NetBox Toolkit plugin."""
|
2
|
+
|
2
3
|
from typing import Dict, Any
|
3
4
|
from django.conf import settings
|
4
5
|
|
5
6
|
|
6
|
-
class
|
7
|
+
class ToolkitSettings:
|
7
8
|
"""Configuration class for toolkit settings."""
|
8
|
-
|
9
|
+
|
9
10
|
# Default connection timeouts
|
10
11
|
DEFAULT_TIMEOUTS = {
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
"socket": 15,
|
13
|
+
"transport": 15,
|
14
|
+
"ops": 30,
|
15
|
+
"banner": 15,
|
16
|
+
"auth": 15,
|
16
17
|
}
|
17
|
-
|
18
|
+
|
18
19
|
# Device-specific timeout overrides
|
19
20
|
DEVICE_TIMEOUTS = {
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
"catalyst": {
|
22
|
+
"socket": 20,
|
23
|
+
"transport": 20,
|
24
|
+
"ops": 45,
|
24
25
|
},
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
"nexus": {
|
27
|
+
"socket": 25,
|
28
|
+
"transport": 25,
|
29
|
+
"ops": 60,
|
29
30
|
},
|
30
31
|
}
|
31
|
-
|
32
|
+
|
32
33
|
# SSH transport options
|
33
34
|
SSH_TRANSPORT_OPTIONS = {
|
34
|
-
|
35
|
-
|
35
|
+
"disabled_algorithms": {
|
36
|
+
"kex": [], # Don't disable any key exchange methods
|
36
37
|
},
|
37
|
-
|
38
|
+
"allowed_kex": [
|
38
39
|
# Modern algorithms
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
"diffie-hellman-group-exchange-sha256",
|
41
|
+
"diffie-hellman-group16-sha512",
|
42
|
+
"diffie-hellman-group18-sha512",
|
43
|
+
"diffie-hellman-group14-sha256",
|
43
44
|
# Legacy algorithms for older devices
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
"diffie-hellman-group-exchange-sha1",
|
46
|
+
"diffie-hellman-group14-sha1",
|
47
|
+
"diffie-hellman-group1-sha1",
|
47
48
|
],
|
48
49
|
}
|
49
|
-
|
50
|
+
|
50
51
|
# Netmiko configuration for fallback connections
|
51
52
|
NETMIKO_CONFIG = {
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
"banner_timeout": 20,
|
54
|
+
"auth_timeout": 20,
|
55
|
+
"global_delay_factor": 1,
|
56
|
+
"use_keys": False, # Disable SSH key authentication
|
57
|
+
"allow_agent": False, # Disable SSH agent
|
57
58
|
# Session logging (disabled by default)
|
58
|
-
|
59
|
+
"session_log": None,
|
59
60
|
# Connection options for legacy devices
|
60
|
-
|
61
|
-
|
62
|
-
|
61
|
+
"fast_cli": False, # Disable for older devices
|
62
|
+
"session_log_record_writes": False,
|
63
|
+
"session_log_file_mode": "write",
|
63
64
|
}
|
64
|
-
|
65
|
+
|
65
66
|
# Retry configuration
|
66
67
|
RETRY_CONFIG = {
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
"max_retries": 2,
|
69
|
+
"retry_delay": 1, # Reduced from 3s to 1s for faster fallback
|
70
|
+
"backoff_multiplier": 1.5, # Reduced from 2 to 1.5 for faster progression
|
70
71
|
}
|
71
|
-
|
72
|
+
|
72
73
|
# Fast connection test timeouts (for initial Scrapli viability testing)
|
73
74
|
FAST_TEST_TIMEOUTS = {
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
"socket": 8, # Reduced from 15s to 8s for faster detection
|
76
|
+
"transport": 8, # Reduced from 15s to 8s for faster detection
|
77
|
+
"ops": 15, # Keep ops timeout reasonable for actual commands
|
77
78
|
}
|
78
|
-
|
79
|
+
|
79
80
|
# Error patterns that should trigger immediate fallback to Netmiko
|
80
81
|
SCRAPLI_FAST_FAIL_PATTERNS = [
|
81
82
|
"No matching key exchange",
|
@@ -83,77 +84,83 @@ class ToolkitConfig:
|
|
83
84
|
"No matching MAC",
|
84
85
|
"connection not opened",
|
85
86
|
"Error reading SSH protocol banner",
|
86
|
-
"Connection refused",
|
87
|
+
"Connection refused",
|
87
88
|
"Operation timed out",
|
88
89
|
"SSH handshake failed",
|
89
90
|
"Protocol version not supported",
|
90
91
|
"Unable to connect to port 22",
|
91
92
|
"Name or service not known",
|
92
|
-
"Network is unreachable"
|
93
|
+
"Network is unreachable",
|
93
94
|
]
|
94
|
-
|
95
|
+
|
95
96
|
# Platform mappings for better recognition
|
96
97
|
PLATFORM_ALIASES = {
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
98
|
+
"ios": "cisco_ios",
|
99
|
+
"iosxe": "cisco_ios",
|
100
|
+
"nxos": "cisco_nxos",
|
101
|
+
"iosxr": "cisco_iosxr",
|
102
|
+
"junos": "juniper_junos",
|
103
|
+
"eos": "arista_eos",
|
103
104
|
}
|
104
|
-
|
105
|
+
|
105
106
|
@classmethod
|
106
107
|
def get_fast_test_timeouts(cls) -> Dict[str, int]:
|
107
108
|
"""Get fast connection test timeouts for initial viability testing."""
|
108
109
|
return cls.FAST_TEST_TIMEOUTS.copy()
|
109
|
-
|
110
|
+
|
110
111
|
@classmethod
|
111
112
|
def should_fast_fail_to_netmiko(cls, error_message: str) -> bool:
|
112
113
|
"""Check if error message indicates immediate fallback to Netmiko is needed."""
|
113
114
|
error_lower = error_message.lower()
|
114
|
-
return any(
|
115
|
-
|
115
|
+
return any(
|
116
|
+
pattern.lower() in error_lower for pattern in cls.SCRAPLI_FAST_FAIL_PATTERNS
|
117
|
+
)
|
118
|
+
|
116
119
|
@classmethod
|
117
|
-
def get_timeouts_for_device(cls, device_type_model: str =
|
120
|
+
def get_timeouts_for_device(cls, device_type_model: str = "") -> Dict[str, int]:
|
118
121
|
"""Get timeout configuration for a specific device type."""
|
119
122
|
timeouts = cls.DEFAULT_TIMEOUTS.copy()
|
120
|
-
|
123
|
+
|
121
124
|
if device_type_model:
|
122
125
|
model_lower = device_type_model.lower()
|
123
126
|
for device_keyword, custom_timeouts in cls.DEVICE_TIMEOUTS.items():
|
124
127
|
if device_keyword in model_lower:
|
125
128
|
timeouts.update(custom_timeouts)
|
126
129
|
break
|
127
|
-
|
130
|
+
|
128
131
|
return timeouts
|
129
|
-
|
132
|
+
|
130
133
|
@classmethod
|
131
134
|
def normalize_platform(cls, platform: str) -> str:
|
132
135
|
"""Normalize platform name using aliases."""
|
133
136
|
if not platform:
|
134
|
-
return
|
135
|
-
|
137
|
+
return ""
|
138
|
+
|
136
139
|
platform_lower = platform.lower()
|
137
140
|
return cls.PLATFORM_ALIASES.get(platform_lower, platform_lower)
|
138
|
-
|
141
|
+
|
139
142
|
@classmethod
|
140
143
|
def get_ssh_options(cls) -> Dict[str, Any]:
|
141
144
|
"""Get SSH transport options."""
|
142
145
|
return cls.SSH_TRANSPORT_OPTIONS.copy()
|
143
|
-
|
146
|
+
|
144
147
|
@classmethod
|
145
148
|
def get_retry_config(cls) -> Dict[str, int]:
|
146
149
|
"""Get retry configuration."""
|
147
150
|
return cls.RETRY_CONFIG.copy()
|
148
|
-
|
151
|
+
|
149
152
|
@classmethod
|
150
153
|
def get_ssh_transport_options(cls) -> Dict[str, Any]:
|
151
154
|
"""Get SSH transport options for Scrapli."""
|
152
|
-
user_config = getattr(settings,
|
153
|
-
|
154
|
-
|
155
|
+
user_config = getattr(settings, "PLUGINS_CONFIG", {}).get(
|
156
|
+
"netbox_toolkit_plugin", {}
|
157
|
+
)
|
158
|
+
return {**cls.SSH_TRANSPORT_OPTIONS, **user_config.get("ssh_options", {})}
|
159
|
+
|
155
160
|
@classmethod
|
156
161
|
def get_netmiko_config(cls) -> Dict[str, Any]:
|
157
162
|
"""Get Netmiko configuration for fallback connections."""
|
158
|
-
user_config = getattr(settings,
|
159
|
-
|
163
|
+
user_config = getattr(settings, "PLUGINS_CONFIG", {}).get(
|
164
|
+
"netbox_toolkit_plugin", {}
|
165
|
+
)
|
166
|
+
return {**cls.NETMIKO_CONFIG, **user_config.get("netmiko", {})}
|