forked from netbox-community/netbox
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement BackgroundJob for running scripts
The independent implementations of interactive and background script execution have been merged into a single BackgroundJob implementation.
- Loading branch information
Showing
6 changed files
with
141 additions
and
208 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import logging | ||
import traceback | ||
from contextlib import nullcontext | ||
|
||
from django.db import transaction | ||
from django.utils.translation import gettext as _ | ||
|
||
from extras.models import Script as ScriptModel | ||
from extras.signals import clear_events | ||
from netbox.context_managers import event_tracking | ||
from utilities.exceptions import AbortScript, AbortTransaction | ||
from utilities.jobs import BackgroundJob | ||
from .utils import is_report | ||
|
||
|
||
class ScriptJob(BackgroundJob): | ||
""" | ||
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. | ||
""" | ||
|
||
@staticmethod | ||
def run_script(script, job, 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: | ||
job: The Job associated with this execution | ||
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() | ||
""" | ||
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(f"Script failed") | ||
raise | ||
|
||
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: | ||
job.data = script.get_job_data() | ||
|
||
@classmethod | ||
def run(cls, job, 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=job.object_id).python_class() | ||
|
||
logger = logging.getLogger(f"netbox.scripts.{script.full_name}") | ||
logger.info(f"Running script (commit={commit})") | ||
|
||
# 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(): | ||
cls.run_script(script, job, request, data, commit) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.