Explorar el Código

Initial work on #3538: script execution API

Jeremy Stretch hace 6 años
padre
commit
1cfb8aea23

+ 30 - 0
netbox/extras/api/serializers.py

@@ -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
 #

+ 3 - 0
netbox/extras/api/urls.py

@@ -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)
 

+ 49 - 0
netbox/extras/api/views.py

@@ -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
 #

+ 18 - 4
netbox/extras/scripts.py

@@ -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)