jobs.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import logging
  2. import traceback
  3. from contextlib import ExitStack
  4. from django.db import transaction
  5. from django.utils.translation import gettext as _
  6. from core.signals import clear_events
  7. from extras.models import Script as ScriptModel
  8. from netbox.jobs import JobRunner
  9. from netbox.registry import registry
  10. from utilities.exceptions import AbortScript, AbortTransaction
  11. from .utils import is_report
  12. class ScriptJob(JobRunner):
  13. """
  14. Script execution job.
  15. A wrapper for calling Script.run(). This performs error handling and provides a hook for committing changes. It
  16. exists outside the Script class to ensure it cannot be overridden by a script author.
  17. """
  18. class Meta:
  19. name = 'Run Script'
  20. def run_script(self, script, request, data, commit):
  21. """
  22. Core script execution task. We capture this within a method to allow for conditionally wrapping it with the
  23. event_tracking context manager (which is bypassed if commit == False).
  24. Args:
  25. request: The WSGI request associated with this execution (if any)
  26. data: A dictionary of data to be passed to the script upon execution
  27. commit: Passed through to Script.run()
  28. """
  29. logger = logging.getLogger(f"netbox.scripts.{script.full_name}")
  30. logger.info(f"Running script (commit={commit})")
  31. try:
  32. try:
  33. with transaction.atomic():
  34. script.output = script.run(data, commit)
  35. if not commit:
  36. raise AbortTransaction()
  37. except AbortTransaction:
  38. script.log_info(message=_("Database changes have been reverted automatically."))
  39. if script.failed:
  40. logger.warning("Script failed")
  41. except Exception as e:
  42. if type(e) is AbortScript:
  43. msg = _("Script aborted with error: ") + str(e)
  44. if is_report(type(script)):
  45. script.log_failure(message=msg)
  46. else:
  47. script.log_failure(msg)
  48. logger.error(f"Script aborted with error: {e}")
  49. else:
  50. stacktrace = traceback.format_exc()
  51. script.log_failure(
  52. message=_("An exception occurred: ") + f"`{type(e).__name__}: {e}`\n```\n{stacktrace}\n```"
  53. )
  54. logger.error(f"Exception raised during script execution: {e}")
  55. if type(e) is not AbortTransaction:
  56. script.log_info(message=_("Database changes have been reverted due to error."))
  57. # Clear all pending events. Job termination (including setting the status) is handled by the job framework.
  58. if request:
  59. clear_events.send(request)
  60. raise
  61. # Update the job data regardless of the execution status of the job. Successes should be reported as well as
  62. # failures.
  63. finally:
  64. self.job.data = script.get_job_data()
  65. def run(self, data, request=None, commit=True, **kwargs):
  66. """
  67. Run the script.
  68. Args:
  69. job: The Job associated with this execution
  70. data: A dictionary of data to be passed to the script upon execution
  71. request: The WSGI request associated with this execution (if any)
  72. commit: Passed through to Script.run()
  73. """
  74. script = ScriptModel.objects.get(pk=self.job.object_id).python_class()
  75. # Add files to form data
  76. if request:
  77. files = request.FILES
  78. for field_name, fileobj in files.items():
  79. data[field_name] = fileobj
  80. # Add the current request as a property of the script
  81. script.request = request
  82. # Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process
  83. # change logging, event rules, etc.
  84. if commit:
  85. with ExitStack() as stack:
  86. for request_processor in registry['request_processors']:
  87. stack.enter_context(request_processor(request))
  88. self.run_script(script, request, data, commit)
  89. else:
  90. self.run_script(script, request, data, commit)