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

Initial work on #3538: script execution API

This commit is contained in:
Jeremy Stretch
2019-10-28 15:02:21 -04:00
parent 657a7aef42
commit 1cfb8aea23
4 changed files with 100 additions and 4 deletions

View File

@ -200,6 +200,36 @@ class ReportDetailSerializer(ReportSerializer):
result = ReportResultSerializer()
#
# Scripts
#
class ScriptSerializer(serializers.Serializer):
id = serializers.SerializerMethodField(read_only=True)
name = serializers.SerializerMethodField(read_only=True)
description = serializers.SerializerMethodField(read_only=True)
vars = serializers.SerializerMethodField(read_only=True)
def get_id(self, instance):
return '{}.{}'.format(instance.__module__, instance.__name__)
def get_name(self, instance):
return getattr(instance.Meta, 'name', instance.__name__)
def get_description(self, instance):
return getattr(instance.Meta, 'description', '')
def get_vars(self, instance):
return {
k: v.__class__.__name__ for k, v in instance._get_vars().items()
}
class ScriptInputSerializer(serializers.Serializer):
data = serializers.JSONField()
commit = serializers.BooleanField()
#
# Change logging
#

View File

@ -38,6 +38,9 @@ router.register(r'config-contexts', views.ConfigContextViewSet)
# Reports
router.register(r'reports', views.ReportViewSet, basename='report')
# Scripts
router.register(r'scripts', views.ScriptViewSet, basename='script')
# Change logging
router.register(r'object-changes', views.ObjectChangeViewSet)

View File

@ -3,6 +3,7 @@ from collections import OrderedDict
from django.contrib.contenttypes.models import ContentType
from django.db.models import Count
from django.http import Http404
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
@ -13,6 +14,7 @@ from extras.models import (
ConfigContext, CustomFieldChoice, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag,
)
from extras.reports import get_report, get_reports
from extras.scripts import get_script, get_scripts
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
from . import serializers
@ -222,6 +224,53 @@ class ReportViewSet(ViewSet):
return Response(serializer.data)
#
# Scripts
#
class ScriptViewSet(ViewSet):
permission_classes = [IsAuthenticatedOrLoginNotRequired]
_ignore_model_permissions = True
exclude_from_schema = True
lookup_value_regex = '[^/]+' # Allow dots
def _get_script(self, pk):
module_name, script_name = pk.split('.')
script = get_script(module_name, script_name)
if script is None:
raise Http404
return script
def list(self, request):
flat_list = []
for script_list in get_scripts().values():
flat_list.extend(script_list.values())
serializer = serializers.ScriptSerializer(flat_list, many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk):
script = self._get_script(pk)
serializer = serializers.ScriptSerializer(script, context={'request': request})
return Response(serializer.data)
def post(self, request, pk):
"""
Run a Script identified as "<module>.<script>".
"""
script = self._get_script(pk)()
serializer = serializers.ScriptInputSerializer(data=request.data)
if serializer.is_valid():
script.run(serializer.data['data'])
return Response(script.log)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#
# Change logging
#

View File

@ -220,16 +220,17 @@ class BaseScript:
def __str__(self):
return getattr(self.Meta, 'name', self.__class__.__name__)
def _get_vars(self):
@classmethod
def _get_vars(cls):
vars = OrderedDict()
# Infer order from Meta.field_order (Python 3.5 and lower)
field_order = getattr(self.Meta, 'field_order', [])
field_order = getattr(cls.Meta, 'field_order', [])
for name in field_order:
vars[name] = getattr(self, name)
vars[name] = getattr(cls, name)
# Default to order of declaration on class
for name, attr in self.__class__.__dict__.items():
for name, attr in cls.__dict__.items():
if name not in vars and issubclass(attr.__class__, ScriptVariable):
vars[name] = attr
@ -361,6 +362,9 @@ def run_script(script, data, files, commit=True):
def get_scripts():
"""
Return a dict of dicts mapping all scripts to their modules.
"""
scripts = OrderedDict()
# Iterate through all modules within the reports path. These are the user-created files in which reports are
@ -375,3 +379,13 @@ def get_scripts():
scripts[module_name] = module_scripts
return scripts
def get_script(module_name, script_name):
"""
Retrieve a script class by module and name. Returns None if the script does not exist.
"""
scripts = get_scripts()
module = scripts.get(module_name)
if module:
return module.get(script_name)