12510 Merge Scripts and Reports (#14976)

* 12510 move reports to use BaseScript

* 12510 merge report into script view

* 12510 add migration for job report to script

* 12510 update templates

* 12510 remove reports

* 12510 cleanup

* 12510 legacy jobs

* 12510 legacy jobs

* 12510 fixes

* 12510 review changes

* 12510 review changes

* 12510 update docs

* 12510 review changes

* 12510 review changes

* 12510 review changes

* 12510 review changes

* 12510 main log results to empty string

* 12510 move migration

* Introduce an internal log level for debug to simplify Script logging

* Misc cleanup

* Remove obsolete is_valid() method

* Reformat script job data (log, output, tests)

* Remove ScriptLogMessageSerializer

* Fix formatting of script logs

* Record a timestamp with script logs

* Rename _current_method to _current_test

* Clean up template

* Remove obsolete runreport management command

* Misc cleanup & refactoring

* Clean up template

* Clean up migration

* Clean up docs

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
Arthur Hanson
2024-02-07 09:02:09 -08:00
committed by GitHub
parent f63d23872f
commit 11697d19a6
26 changed files with 574 additions and 1275 deletions

View File

@@ -16,7 +16,6 @@ from core.choices import JobStatusChoices
from core.models import Job
from extras import filtersets
from extras.models import *
from extras.reports import get_module_and_report, run_report
from extras.scripts import get_module_and_script, run_script
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
from netbox.api.features import SyncedDataMixin
@@ -211,111 +210,6 @@ class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxMo
return self.render_configtemplate(request, configtemplate, context)
#
# Reports
#
class ReportViewSet(ViewSet):
permission_classes = [IsAuthenticatedOrLoginNotRequired]
_ignore_model_permissions = True
schema = None
lookup_value_regex = '[^/]+' # Allow dots
def _get_report(self, pk):
try:
module_name, report_name = pk.split('.', maxsplit=1)
except ValueError:
raise Http404
module, report = get_module_and_report(module_name, report_name)
if report is None:
raise Http404
return module, report
def list(self, request):
"""
Compile all reports and their related results (if any). Result data is deferred in the list view.
"""
results = {
job.name: job
for job in Job.objects.filter(
object_type=ContentType.objects.get(app_label='extras', model='reportmodule'),
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
).order_by('name', '-created').distinct('name').defer('data')
}
report_list = []
for report_module in ReportModule.objects.restrict(request.user):
report_list.extend([report() for report in report_module.reports.values()])
# Attach Job objects to each report (if any)
for report in report_list:
report.result = results.get(report.name, None)
serializer = serializers.ReportSerializer(report_list, many=True, context={
'request': request,
})
return Response({'count': len(report_list), 'results': serializer.data})
def retrieve(self, request, pk):
"""
Retrieve a single Report identified as "<module>.<report>".
"""
module, report = self._get_report(pk)
# Retrieve the Report and Job, if any.
object_type = ContentType.objects.get(app_label='extras', model='reportmodule')
report.result = Job.objects.filter(
object_type=object_type,
name=report.name,
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
).first()
serializer = serializers.ReportDetailSerializer(report, context={
'request': request
})
return Response(serializer.data)
@action(detail=True, methods=['post'])
def run(self, request, pk):
"""
Run a Report identified as "<module>.<script>" and return the pending Job as the result
"""
# Check that the user has permission to run reports.
if not request.user.has_perm('extras.run_report'):
raise PermissionDenied("This user does not have permission to run reports.")
# Check that at least one RQ worker is running
if not Worker.count(get_connection('default')):
raise RQWorkerNotRunningException()
# Retrieve and run the Report. This will create a new Job.
module, report_cls = self._get_report(pk)
report = report_cls
input_serializer = serializers.ReportInputSerializer(
data=request.data,
context={'report': report}
)
if input_serializer.is_valid():
report.result = Job.enqueue(
run_report,
instance=module,
name=report.class_name,
user=request.user,
job_timeout=report.job_timeout,
schedule_at=input_serializer.validated_data.get('schedule_at'),
interval=input_serializer.validated_data.get('interval')
)
serializer = serializers.ReportDetailSerializer(report, context={'request': request})
return Response(serializer.data)
return Response(input_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#
# Scripts
#