mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-26 01:06:11 -06:00
Accept a recurrence interval when executing scripts & reports
This commit is contained in:
parent
d505c723dc
commit
05780891ed
@ -414,6 +414,7 @@ class ReportDetailSerializer(ReportSerializer):
|
|||||||
|
|
||||||
class ReportInputSerializer(serializers.Serializer):
|
class ReportInputSerializer(serializers.Serializer):
|
||||||
schedule_at = serializers.DateTimeField(required=False, allow_null=True)
|
schedule_at = serializers.DateTimeField(required=False, allow_null=True)
|
||||||
|
interval = serializers.IntegerField(required=False, allow_null=True)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -448,6 +449,7 @@ class ScriptInputSerializer(serializers.Serializer):
|
|||||||
data = serializers.JSONField()
|
data = serializers.JSONField()
|
||||||
commit = serializers.BooleanField()
|
commit = serializers.BooleanField()
|
||||||
schedule_at = serializers.DateTimeField(required=False, allow_null=True)
|
schedule_at = serializers.DateTimeField(required=False, allow_null=True)
|
||||||
|
interval = serializers.IntegerField(required=False, allow_null=True)
|
||||||
|
|
||||||
|
|
||||||
class ScriptLogMessageSerializer(serializers.Serializer):
|
class ScriptLogMessageSerializer(serializers.Serializer):
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Q
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django_rq.queues import get_connection
|
from django_rq.queues import get_connection
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -246,16 +245,14 @@ class ReportViewSet(ViewSet):
|
|||||||
input_serializer = serializers.ReportInputSerializer(data=request.data)
|
input_serializer = serializers.ReportInputSerializer(data=request.data)
|
||||||
|
|
||||||
if input_serializer.is_valid():
|
if input_serializer.is_valid():
|
||||||
schedule_at = input_serializer.validated_data.get('schedule_at')
|
|
||||||
|
|
||||||
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
|
||||||
job_result = JobResult.enqueue_job(
|
job_result = JobResult.enqueue_job(
|
||||||
run_report,
|
run_report,
|
||||||
report.full_name,
|
name=report.full_name,
|
||||||
report_content_type,
|
obj_type=ContentType.objects.get_for_model(Report),
|
||||||
request.user,
|
user=request.user,
|
||||||
job_timeout=report.job_timeout,
|
job_timeout=report.job_timeout,
|
||||||
schedule_at=schedule_at,
|
schedule_at=input_serializer.validated_data.get('schedule_at'),
|
||||||
|
interval=input_serializer.validated_data.get('interval')
|
||||||
)
|
)
|
||||||
report.result = job_result
|
report.result = job_result
|
||||||
|
|
||||||
@ -329,21 +326,17 @@ class ScriptViewSet(ViewSet):
|
|||||||
raise RQWorkerNotRunningException()
|
raise RQWorkerNotRunningException()
|
||||||
|
|
||||||
if input_serializer.is_valid():
|
if input_serializer.is_valid():
|
||||||
data = input_serializer.data['data']
|
|
||||||
commit = input_serializer.data['commit']
|
|
||||||
schedule_at = input_serializer.validated_data.get('schedule_at')
|
|
||||||
|
|
||||||
script_content_type = ContentType.objects.get(app_label='extras', model='script')
|
|
||||||
job_result = JobResult.enqueue_job(
|
job_result = JobResult.enqueue_job(
|
||||||
run_script,
|
run_script,
|
||||||
script.full_name,
|
name=script.full_name,
|
||||||
script_content_type,
|
obj_type=ContentType.objects.get_for_model(Script),
|
||||||
request.user,
|
user=request.user,
|
||||||
data=data,
|
data=input_serializer.data['data'],
|
||||||
request=copy_safe_request(request),
|
request=copy_safe_request(request),
|
||||||
commit=commit,
|
commit=input_serializer.data['commit'],
|
||||||
job_timeout=script.job_timeout,
|
job_timeout=script.job_timeout,
|
||||||
schedule_at=schedule_at,
|
schedule_at=input_serializer.validated_data.get('schedule_at'),
|
||||||
|
interval=input_serializer.validated_data.get('interval')
|
||||||
)
|
)
|
||||||
script.result = job_result
|
script.result = job_result
|
||||||
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
|
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
|
||||||
|
@ -15,3 +15,8 @@ class ReportForm(BootstrapMixin, forms.Form):
|
|||||||
label=_("Schedule at"),
|
label=_("Schedule at"),
|
||||||
help_text=_("Schedule execution of report to a set time"),
|
help_text=_("Schedule execution of report to a set time"),
|
||||||
)
|
)
|
||||||
|
interval = forms.IntegerField(
|
||||||
|
required=False,
|
||||||
|
label=_("Recurs every"),
|
||||||
|
help_text=_("Interval at which this report is re-run (in minutes)")
|
||||||
|
)
|
||||||
|
@ -21,19 +21,26 @@ class ScriptForm(BootstrapMixin, forms.Form):
|
|||||||
label=_("Schedule at"),
|
label=_("Schedule at"),
|
||||||
help_text=_("Schedule execution of script to a set time"),
|
help_text=_("Schedule execution of script to a set time"),
|
||||||
)
|
)
|
||||||
|
_interval = forms.IntegerField(
|
||||||
|
required=False,
|
||||||
|
label=_("Recurs every"),
|
||||||
|
help_text=_("Interval at which this script is re-run (in minutes)")
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Move _commit and _schedule_at to the end of the form
|
# Move _commit and _schedule_at to the end of the form
|
||||||
schedule_at = self.fields.pop('_schedule_at')
|
schedule_at = self.fields.pop('_schedule_at')
|
||||||
|
interval = self.fields.pop('_interval')
|
||||||
commit = self.fields.pop('_commit')
|
commit = self.fields.pop('_commit')
|
||||||
self.fields['_schedule_at'] = schedule_at
|
self.fields['_schedule_at'] = schedule_at
|
||||||
|
self.fields['_interval'] = interval
|
||||||
self.fields['_commit'] = commit
|
self.fields['_commit'] = commit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def requires_input(self):
|
def requires_input(self):
|
||||||
"""
|
"""
|
||||||
A boolean indicating whether the form requires user input (ignore the _commit and _schedule_at fields).
|
A boolean indicating whether the form requires user input (ignore the built-in fields).
|
||||||
"""
|
"""
|
||||||
return bool(len(self.fields) > 2)
|
return bool(len(self.fields) > 3)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import importlib
|
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import traceback
|
import traceback
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -11,7 +11,6 @@ from django_rq import job
|
|||||||
from .choices import JobResultStatusChoices, LogLevelChoices
|
from .choices import JobResultStatusChoices, LogLevelChoices
|
||||||
from .models import JobResult
|
from .models import JobResult
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -85,10 +84,23 @@ def run_report(job_result, *args, **kwargs):
|
|||||||
try:
|
try:
|
||||||
job_result.start()
|
job_result.start()
|
||||||
report.run(job_result)
|
report.run(job_result)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
|
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
|
||||||
job_result.save()
|
job_result.save()
|
||||||
logging.error(f"Error during execution of report {job_result.name}")
|
logging.error(f"Error during execution of report {job_result.name}")
|
||||||
|
finally:
|
||||||
|
# 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)
|
||||||
|
JobResult.enqueue_job(
|
||||||
|
run_report,
|
||||||
|
name=job_result.name,
|
||||||
|
obj_type=job_result.obj_type,
|
||||||
|
user=job_result.user,
|
||||||
|
job_timeout=report.job_timeout,
|
||||||
|
schedule_at=new_scheduled_time,
|
||||||
|
interval=job_result.interval
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Report(object):
|
class Report(object):
|
||||||
|
@ -4,8 +4,9 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
|
||||||
import threading
|
import threading
|
||||||
|
import traceback
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from django import forms
|
from django import forms
|
||||||
@ -16,6 +17,7 @@ from django.utils.functional import classproperty
|
|||||||
|
|
||||||
from extras.api.serializers import ScriptOutputSerializer
|
from extras.api.serializers import ScriptOutputSerializer
|
||||||
from extras.choices import JobResultStatusChoices, LogLevelChoices
|
from extras.choices import JobResultStatusChoices, LogLevelChoices
|
||||||
|
from extras.models import JobResult
|
||||||
from extras.signals import clear_webhooks
|
from extras.signals import clear_webhooks
|
||||||
from ipam.formfields import IPAddressFormField, IPNetworkFormField
|
from ipam.formfields import IPAddressFormField, IPNetworkFormField
|
||||||
from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
|
from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
|
||||||
@ -491,6 +493,22 @@ def run_script(data, request, commit=True, *args, **kwargs):
|
|||||||
else:
|
else:
|
||||||
_run_script()
|
_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)
|
||||||
|
JobResult.enqueue_job(
|
||||||
|
run_script,
|
||||||
|
name=job_result.name,
|
||||||
|
obj_type=job_result.obj_type,
|
||||||
|
user=job_result.user,
|
||||||
|
schedule_at=new_scheduled_time,
|
||||||
|
interval=job_result.interval,
|
||||||
|
job_timeout=script.job_timeout,
|
||||||
|
data=data,
|
||||||
|
request=request,
|
||||||
|
commit=commit
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_scripts(use_names=False):
|
def get_scripts(use_names=False):
|
||||||
"""
|
"""
|
||||||
|
@ -676,7 +676,6 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
|
|||||||
form = ReportForm(request.POST)
|
form = ReportForm(request.POST)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
schedule_at = form.cleaned_data.get("schedule_at")
|
|
||||||
|
|
||||||
# Allow execution only if RQ worker process is running
|
# Allow execution only if RQ worker process is running
|
||||||
if not Worker.count(get_connection('default')):
|
if not Worker.count(get_connection('default')):
|
||||||
@ -686,14 +685,14 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Run the Report. A new JobResult is created.
|
# Run the Report. A new JobResult is created.
|
||||||
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
|
||||||
job_result = JobResult.enqueue_job(
|
job_result = JobResult.enqueue_job(
|
||||||
run_report,
|
run_report,
|
||||||
report.full_name,
|
name=report.full_name,
|
||||||
report_content_type,
|
obj_type=ContentType.objects.get_for_model(Report),
|
||||||
request.user,
|
user=request.user,
|
||||||
job_timeout=report.job_timeout,
|
schedule_at=form.cleaned_data.get('schedule_at'),
|
||||||
schedule_at=schedule_at,
|
interval=form.cleaned_data.get('interval'),
|
||||||
|
job_timeout=report.job_timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect('extras:report_result', job_result_pk=job_result.pk)
|
return redirect('extras:report_result', job_result_pk=job_result.pk)
|
||||||
@ -787,9 +786,8 @@ class ScriptView(ContentTypePermissionRequiredMixin, GetScriptMixin, View):
|
|||||||
form = script.as_form(initial=normalize_querydict(request.GET))
|
form = script.as_form(initial=normalize_querydict(request.GET))
|
||||||
|
|
||||||
# Look for a pending JobResult (use the latest one by creation timestamp)
|
# Look for a pending JobResult (use the latest one by creation timestamp)
|
||||||
script_content_type = ContentType.objects.get(app_label='extras', model='script')
|
|
||||||
script.result = JobResult.objects.filter(
|
script.result = JobResult.objects.filter(
|
||||||
obj_type=script_content_type,
|
obj_type=ContentType.objects.get_for_model(Script),
|
||||||
name=script.full_name,
|
name=script.full_name,
|
||||||
).exclude(
|
).exclude(
|
||||||
status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
|
status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
|
||||||
@ -815,21 +813,17 @@ class ScriptView(ContentTypePermissionRequiredMixin, GetScriptMixin, View):
|
|||||||
messages.error(request, "Unable to run script: RQ worker process not running.")
|
messages.error(request, "Unable to run script: RQ worker process not running.")
|
||||||
|
|
||||||
elif form.is_valid():
|
elif form.is_valid():
|
||||||
commit = form.cleaned_data.pop('_commit')
|
|
||||||
schedule_at = form.cleaned_data.pop("_schedule_at")
|
|
||||||
|
|
||||||
script_content_type = ContentType.objects.get(app_label='extras', model='script')
|
|
||||||
|
|
||||||
job_result = JobResult.enqueue_job(
|
job_result = JobResult.enqueue_job(
|
||||||
run_script,
|
run_script,
|
||||||
script.full_name,
|
name=script.full_name,
|
||||||
script_content_type,
|
obj_type=ContentType.objects.get_for_model(Script),
|
||||||
request.user,
|
user=request.user,
|
||||||
|
schedule_at=form.cleaned_data.pop('_schedule_at'),
|
||||||
|
interval=form.cleaned_data.pop('_interval'),
|
||||||
data=form.cleaned_data,
|
data=form.cleaned_data,
|
||||||
request=copy_safe_request(request),
|
request=copy_safe_request(request),
|
||||||
commit=commit,
|
|
||||||
job_timeout=script.job_timeout,
|
job_timeout=script.job_timeout,
|
||||||
schedule_at=schedule_at,
|
commit=form.cleaned_data.pop('_commit')
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect('extras:script_result', job_result_pk=job_result.pk)
|
return redirect('extras:script_result', job_result_pk=job_result.pk)
|
||||||
|
Loading…
Reference in New Issue
Block a user