mirror of
https://github.com/netbox-community/netbox.git
synced 2026-04-20 16:01:34 +02:00
* Fix non-null constraint for script execution Withc34a0e2, validation of job object fields is enabled, so ScriptJob must not set required fields to empty strings. This commit revertsb18f193and (hopefully) fixes this issue not only for UI views, but for all interactions with scripts. Fixes: #17923 * Fix name of recurring jobs For recurring jobs, the name must be passed to the next job object when the job is rescheduled.
105 lines
4.0 KiB
Python
105 lines
4.0 KiB
Python
import logging
|
|
import traceback
|
|
from contextlib import nullcontext
|
|
|
|
from django.db import transaction
|
|
from django.utils.translation import gettext as _
|
|
|
|
from core.signals import clear_events
|
|
from extras.models import Script as ScriptModel
|
|
from netbox.context_managers import event_tracking
|
|
from netbox.jobs import JobRunner
|
|
from utilities.exceptions import AbortScript, AbortTransaction
|
|
from .utils import is_report
|
|
|
|
|
|
class ScriptJob(JobRunner):
|
|
"""
|
|
Script execution job.
|
|
|
|
A wrapper for calling Script.run(). This performs error handling and provides a hook for committing changes. It
|
|
exists outside the Script class to ensure it cannot be overridden by a script author.
|
|
"""
|
|
|
|
class Meta:
|
|
name = 'Run Script'
|
|
|
|
def run_script(self, script, request, data, commit):
|
|
"""
|
|
Core script execution task. We capture this within a method to allow for conditionally wrapping it with the
|
|
event_tracking context manager (which is bypassed if commit == False).
|
|
|
|
Args:
|
|
request: The WSGI request associated with this execution (if any)
|
|
data: A dictionary of data to be passed to the script upon execution
|
|
commit: Passed through to Script.run()
|
|
"""
|
|
logger = logging.getLogger(f"netbox.scripts.{script.full_name}")
|
|
logger.info(f"Running script (commit={commit})")
|
|
|
|
try:
|
|
try:
|
|
with transaction.atomic():
|
|
script.output = script.run(data, commit)
|
|
if not commit:
|
|
raise AbortTransaction()
|
|
except AbortTransaction:
|
|
script.log_info(message=_("Database changes have been reverted automatically."))
|
|
if script.failed:
|
|
logger.warning("Script failed")
|
|
|
|
except Exception as e:
|
|
if type(e) is AbortScript:
|
|
msg = _("Script aborted with error: ") + str(e)
|
|
if is_report(type(script)):
|
|
script.log_failure(message=msg)
|
|
else:
|
|
script.log_failure(msg)
|
|
logger.error(f"Script aborted with error: {e}")
|
|
|
|
else:
|
|
stacktrace = traceback.format_exc()
|
|
script.log_failure(
|
|
message=_("An exception occurred: ") + f"`{type(e).__name__}: {e}`\n```\n{stacktrace}\n```"
|
|
)
|
|
logger.error(f"Exception raised during script execution: {e}")
|
|
|
|
if type(e) is not AbortTransaction:
|
|
script.log_info(message=_("Database changes have been reverted due to error."))
|
|
|
|
# Clear all pending events. Job termination (including setting the status) is handled by the job framework.
|
|
if request:
|
|
clear_events.send(request)
|
|
raise
|
|
|
|
# Update the job data regardless of the execution status of the job. Successes should be reported as well as
|
|
# failures.
|
|
finally:
|
|
self.job.data = script.get_job_data()
|
|
|
|
def run(self, data, request=None, commit=True, **kwargs):
|
|
"""
|
|
Run the script.
|
|
|
|
Args:
|
|
job: The Job associated with this execution
|
|
data: A dictionary of data to be passed to the script upon execution
|
|
request: The WSGI request associated with this execution (if any)
|
|
commit: Passed through to Script.run()
|
|
"""
|
|
script = ScriptModel.objects.get(pk=self.job.object_id).python_class()
|
|
|
|
# Add files to form data
|
|
if request:
|
|
files = request.FILES
|
|
for field_name, fileobj in files.items():
|
|
data[field_name] = fileobj
|
|
|
|
# Add the current request as a property of the script
|
|
script.request = request
|
|
|
|
# Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process
|
|
# change logging, event rules, etc.
|
|
with event_tracking(request) if commit else nullcontext():
|
|
self.run_script(script, request, data, commit)
|