1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #12068: Establish a direct relationship from jobs to objects (#12075)

* Reference database object by GFK when running scripts & reports via UI

* Reference database object by GFK when running scripts & reports via API

* Remove old enqueue_job() method

* Enable filtering jobs by object

* Introduce ObjectJobsView

* Add tabbed views for report & script jobs

* Add object_id to JobSerializer

* Move generic relation to JobsMixin

* Clean up old naming
This commit is contained in:
Jeremy Stretch
2023-03-28 15:47:09 -04:00
committed by GitHub
parent 15590f1f48
commit d2a694a878
33 changed files with 583 additions and 353 deletions

View File

@ -25,7 +25,7 @@ from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicMo
from .context_managers import change_logging
from .forms import ScriptForm
__all__ = [
__all__ = (
'BaseScript',
'BooleanVar',
'ChoiceVar',
@ -40,7 +40,9 @@ __all__ = [
'Script',
'StringVar',
'TextVar',
]
'get_module_and_script',
'run_script',
)
#
@ -436,18 +438,23 @@ def is_variable(obj):
return isinstance(obj, ScriptVariable)
def run_script(data, request, commit=True, *args, **kwargs):
def get_module_and_script(module_name, script_name):
module = ScriptModule.objects.get(file_path=f'{module_name}.py')
script = module.scripts.get(script_name)
return module, script
def run_script(data, request, job, commit=True, **kwargs):
"""
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.
"""
job_result = kwargs.pop('job_result')
job_result.start()
job.start()
module_name, script_name = job_result.name.split('.', 1)
script = get_script(module_name, script_name)()
module = ScriptModule.objects.get(pk=job.object_id)
script = module.scripts.get(job.name)()
logger = logging.getLogger(f"netbox.scripts.{module_name}.{script_name}")
logger = logging.getLogger(f"netbox.scripts.{script.full_name}")
logger.info(f"Running script (commit={commit})")
# Add files to form data
@ -472,8 +479,8 @@ def run_script(data, request, commit=True, *args, **kwargs):
except AbortTransaction:
script.log_info("Database changes have been reverted automatically.")
clear_webhooks.send(request)
job_result.data = ScriptOutputSerializer(script).data
job_result.terminate()
job.data = ScriptOutputSerializer(script).data
job.terminate()
except Exception as e:
if type(e) is AbortScript:
script.log_failure(f"Script aborted with error: {e}")
@ -483,11 +490,11 @@ def run_script(data, request, commit=True, *args, **kwargs):
script.log_failure(f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```")
logger.error(f"Exception raised during script execution: {e}")
script.log_info("Database changes have been reverted due to error.")
job_result.data = ScriptOutputSerializer(script).data
job_result.terminate(status=JobStatusChoices.STATUS_ERRORED)
job.data = ScriptOutputSerializer(script).data
job.terminate(status=JobStatusChoices.STATUS_ERRORED)
clear_webhooks.send(request)
logger.info(f"Script completed in {job_result.duration}")
logger.info(f"Script completed in {job.duration}")
# Execute the script. If commit is True, wrap it with the change_logging context manager to ensure we process
# change logging, webhooks, etc.
@ -498,25 +505,17 @@ def run_script(data, request, commit=True, *args, **kwargs):
_run_script()
# Schedule the next job if an interval has been set
if job_result.interval:
new_scheduled_time = job_result.scheduled + timedelta(minutes=job_result.interval)
Job.enqueue_job(
if job.interval:
new_scheduled_time = job.scheduled + timedelta(minutes=job.interval)
Job.enqueue(
run_script,
name=job_result.name,
obj_type=job_result.obj_type,
user=job_result.user,
instance=job.object,
name=job.name,
user=job.user,
schedule_at=new_scheduled_time,
interval=job_result.interval,
interval=job.interval,
job_timeout=script.job_timeout,
data=data,
request=request,
commit=commit
)
def get_script(module_name, script_name):
"""
Retrieve a script class by module and name. Returns None if the script does not exist.
"""
module = ScriptModule.objects.get(file_path=f'{module_name}.py')
return module.scripts.get(script_name)