Skip to content

Commit 4cb8ca3

Browse files
committed
Improve error handling of script loading
* WIP script sync
1 parent 3097db4 commit 4cb8ca3

File tree

4 files changed

+48
-8
lines changed

4 files changed

+48
-8
lines changed

netbox_script_manager/api/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def load(self, request):
5050
if not request.user.has_perm(permission):
5151
raise PermissionDenied(f"Missing permission: {permission}")
5252

53-
scripts = util.load_scripts()
53+
scripts, _ = util.load_scripts()
5454
script_instances = {script_instance.script_path: script_instance for script_instance in ScriptInstance.objects.all()}
5555
loaded_scripts = []
5656

netbox_script_manager/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
),
2020
path("script-instances/delete/", views.ScriptInstanceBulkDeleteView.as_view(), name="scriptinstance_bulk_delete"),
2121
path("script-instances/load/", views.ScriptInstanceLoadView.as_view(), name="scriptinstance_load"),
22+
path("script-instances/sync/", views.ScriptInstanceSyncView.as_view(), name="scriptinstance_sync"),
2223
# ScriptExecution
2324
path("script-executions/", views.ScriptExecutionListView.as_view(), name="scriptexecution_list"),
2425
path("script-executions/<int:pk>/", views.ScriptExecutionView.as_view(), name="scriptexecution"),

netbox_script_manager/util.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import inspect
2+
import logging
23
import pkgutil
34
import sys
45
import threading
56

67
from django.conf import settings
78
from utilities.utils import normalize_querydict
89

10+
logger = logging.getLogger("netbox.plugins.netbox_script_manager")
11+
912
# Fields not included when saving script input
1013
EXCLUDED_POST_FIELDS = ["csrfmiddlewaretoken", "_schedule_at", "_interval", "_run"]
1114

@@ -56,6 +59,7 @@ def load_scripts():
5659

5760
scripts = {}
5861
modules = list(pkgutil.iter_modules([custom_script_root]))
62+
failed_modules = {}
5963

6064
# Iterate over all modules in the custom script root
6165
for importer, module_name, _ in modules:
@@ -66,17 +70,16 @@ def load_scripts():
6670
# Manually load the module
6771
module = importer.find_module(module_name).load_module(module_name)
6872
except Exception as e:
69-
# TODO: Error handling
70-
print(f"Failed to load module {module_name}: {e}")
71-
import traceback
72-
73-
traceback.print_exc()
73+
failed_modules[module_name] = e
74+
logger.warning(f"Failed to load module {module_name}: {e}")
75+
logger.exception(e)
76+
continue
7477

7578
# Find all CustomScript members
7679
for name, cls in inspect.getmembers(module, is_script):
7780
scripts[f"{module.__name__}.{name}"] = cls
7881

79-
return scripts
82+
return scripts, failed_modules
8083

8184

8285
def clear_module_cache():

netbox_script_manager/views.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import io
12
import json
23
import uuid
34

@@ -7,6 +8,7 @@
78
from django.contrib.contenttypes.models import ContentType
89
from django.http import HttpResponse
910
from django.shortcuts import redirect, render
11+
from django.utils.safestring import mark_safe
1012
from django.views.generic import View
1113
from extras.filtersets import ObjectChangeFilterSet
1214
from extras.models import ObjectChange
@@ -20,6 +22,7 @@
2022
from .choices import ScriptExecutionStatusChoices
2123
from .models import ScriptExecution
2224
from .scripts import run_script
25+
from .templatetags.scriptmanager import format_exception
2326

2427
plugin_config = settings.PLUGINS_CONFIG.get("netbox_script_manager")
2528

@@ -129,7 +132,7 @@ def get_required_permission(self):
129132
def get(self, request):
130133
scripts_found = False
131134

132-
scripts = util.load_scripts()
135+
scripts, failed_modules = util.load_scripts()
133136
script_instances = {script_instance.script_path: script_instance for script_instance in models.ScriptInstance.objects.all()}
134137

135138
for script_path, script in scripts.items():
@@ -153,12 +156,45 @@ def get(self, request):
153156

154157
messages.success(request, f'Script "{script_name}" loaded')
155158

159+
for module_name, exception in failed_modules.items():
160+
# This is hackish but it works. Toast messages are kinda limited in netbox.
161+
messages.error(
162+
request,
163+
mark_safe(
164+
f'Failed to load module {module_name}: <pre style="overflow-x: scroll; width: 350px;">{format_exception(exception)}</pre>'
165+
),
166+
)
167+
156168
if not scripts_found:
157169
messages.info(request, "No new scripts found")
158170

159171
return redirect("plugins:netbox_script_manager:scriptinstance_list")
160172

161173

174+
class ScriptInstanceSyncView(ContentTypePermissionRequiredMixin, View):
175+
def get_required_permission(self):
176+
return "netbox_script_manager.sync_scriptinstance"
177+
178+
def get(self, request):
179+
try:
180+
from dulwich import porcelain
181+
except ImportError:
182+
messages.error(request, "Dulwich is not installed")
183+
return redirect("plugins:netbox_script_manager:scriptinstance_list")
184+
185+
script_root = plugin_config.get("SCRIPT_ROOT")
186+
187+
output_io = io.StringIO()
188+
output = porcelain.pull(script_root, outstream=output_io)
189+
message = [f"Pulled git repository: {script_root} {output_io.getvalue()}"]
190+
191+
if output_io:
192+
message.append(output_io.getvalue())
193+
194+
messages.info(request, "\n".join(message))
195+
return redirect("plugins:netbox_script_manager:scriptinstance_list")
196+
197+
162198
@register_model_view(models.ScriptInstance, "execution")
163199
class ScriptInstanceScriptExecutionsView(generic.ObjectChildrenView):
164200
queryset = models.ScriptInstance.objects.all()

0 commit comments

Comments
 (0)