2023-12-21 07:42:09 +00:00
|
|
|
import os
|
2022-12-20 08:48:18 +00:00
|
|
|
import os.path
|
2023-12-21 07:42:09 +00:00
|
|
|
import re
|
2022-10-25 04:57:34 +00:00
|
|
|
import shutil
|
|
|
|
import zipfile
|
2022-11-02 07:01:52 +00:00
|
|
|
from typing import Callable
|
2022-10-25 11:31:13 +00:00
|
|
|
|
2022-12-20 08:48:18 +00:00
|
|
|
from django.conf import settings
|
2022-11-02 03:08:13 +00:00
|
|
|
from django.core.files.storage import default_storage
|
2022-12-20 08:48:18 +00:00
|
|
|
from django.http import HttpResponse
|
2023-01-16 11:02:09 +00:00
|
|
|
from django.shortcuts import get_object_or_404
|
2023-01-30 07:19:05 +00:00
|
|
|
from django.utils.translation import gettext as _
|
2022-11-02 07:01:52 +00:00
|
|
|
from rest_framework import viewsets
|
2022-10-25 04:57:34 +00:00
|
|
|
from rest_framework.decorators import action
|
2022-11-01 12:37:04 +00:00
|
|
|
from rest_framework.request import Request
|
2022-10-25 04:57:34 +00:00
|
|
|
from rest_framework.response import Response
|
2022-10-25 11:31:13 +00:00
|
|
|
from rest_framework.serializers import ValidationError
|
2022-10-25 04:57:34 +00:00
|
|
|
|
2023-01-16 11:02:09 +00:00
|
|
|
from common.api import JMSBulkModelViewSet
|
|
|
|
from common.serializers import FileSerializer
|
2022-12-20 08:48:18 +00:00
|
|
|
from common.utils import is_uuid
|
2022-10-28 10:19:44 +00:00
|
|
|
from terminal import serializers
|
|
|
|
from terminal.models import AppletPublication, Applet
|
2022-10-25 04:57:34 +00:00
|
|
|
|
2022-10-28 10:19:44 +00:00
|
|
|
__all__ = ['AppletViewSet', 'AppletPublicationViewSet']
|
|
|
|
|
|
|
|
|
2022-11-01 12:37:04 +00:00
|
|
|
class DownloadUploadMixin:
|
|
|
|
get_serializer: Callable
|
|
|
|
request: Request
|
|
|
|
get_object: Callable
|
2022-10-25 11:31:13 +00:00
|
|
|
|
|
|
|
def extract_and_check_file(self, request):
|
|
|
|
serializer = self.get_serializer(data=self.request.data)
|
2022-10-25 04:57:34 +00:00
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
|
|
|
|
file = serializer.validated_data['file']
|
|
|
|
save_to = 'applets/{}'.format(file.name + '.tmp.zip')
|
|
|
|
if default_storage.exists(save_to):
|
|
|
|
default_storage.delete(save_to)
|
|
|
|
rel_path = default_storage.save(save_to, file)
|
|
|
|
path = default_storage.path(rel_path)
|
|
|
|
extract_to = default_storage.path('applets/{}.tmp'.format(file.name))
|
|
|
|
if os.path.exists(extract_to):
|
|
|
|
shutil.rmtree(extract_to)
|
|
|
|
|
2023-01-30 07:19:05 +00:00
|
|
|
try:
|
|
|
|
with zipfile.ZipFile(path) as zp:
|
|
|
|
if zp.testzip() is not None:
|
|
|
|
raise ValidationError({'error': _('Invalid zip file')})
|
|
|
|
zp.extractall(extract_to)
|
|
|
|
except RuntimeError as e:
|
|
|
|
raise ValidationError({'error': _('Invalid zip file') + ': {}'.format(e)})
|
2022-10-25 04:57:34 +00:00
|
|
|
|
|
|
|
tmp_dir = os.path.join(extract_to, file.name.replace('.zip', ''))
|
2023-12-21 07:42:09 +00:00
|
|
|
if not os.path.exists(tmp_dir):
|
|
|
|
name = file.name
|
|
|
|
name = re.match(r"(\w+)", name).group()
|
|
|
|
tmp_dir = os.path.join(extract_to, name)
|
|
|
|
|
2022-12-20 08:48:18 +00:00
|
|
|
manifest = Applet.validate_pkg(tmp_dir)
|
2022-10-25 11:31:13 +00:00
|
|
|
return manifest, tmp_dir
|
|
|
|
|
2022-11-03 08:55:38 +00:00
|
|
|
@action(detail=False, methods=['post'], serializer_class=FileSerializer)
|
2022-10-25 11:31:13 +00:00
|
|
|
def upload(self, request, *args, **kwargs):
|
|
|
|
manifest, tmp_dir = self.extract_and_check_file(request)
|
|
|
|
name = manifest['name']
|
|
|
|
update = request.query_params.get('update')
|
|
|
|
|
2023-08-17 02:54:35 +00:00
|
|
|
is_enterprise = manifest.get('edition') == Applet.Edition.enterprise
|
2023-10-18 06:10:24 +00:00
|
|
|
if is_enterprise and not settings.XPACK_LICENSE_IS_VALID:
|
2023-08-17 02:54:35 +00:00
|
|
|
raise ValidationError({'error': _('This is enterprise edition applet')})
|
|
|
|
|
2022-10-28 10:19:44 +00:00
|
|
|
instance = Applet.objects.filter(name=name).first()
|
2022-10-25 04:57:34 +00:00
|
|
|
if instance and not update:
|
|
|
|
return Response({'error': 'Applet already exists: {}'.format(name)}, status=400)
|
|
|
|
|
2023-04-14 03:19:22 +00:00
|
|
|
applet, serializer = Applet.install_from_dir(tmp_dir, builtin=False)
|
2022-10-25 04:57:34 +00:00
|
|
|
return Response(serializer.data, status=201)
|
|
|
|
|
2022-11-01 10:40:42 +00:00
|
|
|
@action(detail=True, methods=['get'])
|
|
|
|
def download(self, request, *args, **kwargs):
|
2022-11-01 12:37:04 +00:00
|
|
|
instance = self.get_object()
|
2022-12-20 08:48:18 +00:00
|
|
|
if instance.builtin:
|
|
|
|
path = os.path.join(settings.APPS_DIR, 'terminal', 'applets', instance.name)
|
|
|
|
else:
|
|
|
|
path = default_storage.path('applets/{}'.format(instance.name))
|
2022-11-01 10:40:42 +00:00
|
|
|
zip_path = shutil.make_archive(path, 'zip', path)
|
|
|
|
with open(zip_path, 'rb') as f:
|
|
|
|
response = HttpResponse(f.read(), status=200, content_type='application/octet-stream')
|
|
|
|
response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'{}.zip'.format(instance.name)
|
2022-12-20 09:52:08 +00:00
|
|
|
os.unlink(zip_path)
|
2022-11-01 10:40:42 +00:00
|
|
|
return response
|
|
|
|
|
2022-10-25 04:57:34 +00:00
|
|
|
|
2023-01-16 11:02:09 +00:00
|
|
|
class AppletViewSet(DownloadUploadMixin, JMSBulkModelViewSet):
|
2022-11-01 12:37:04 +00:00
|
|
|
queryset = Applet.objects.all()
|
|
|
|
serializer_class = serializers.AppletSerializer
|
2023-01-30 05:01:06 +00:00
|
|
|
filterset_fields = ['name', 'version', 'builtin', 'is_active']
|
2023-01-20 09:52:51 +00:00
|
|
|
search_fields = ['name', 'display_name', 'author']
|
2022-11-01 12:37:04 +00:00
|
|
|
rbac_perms = {
|
|
|
|
'upload': 'terminal.add_applet',
|
|
|
|
'download': 'terminal.view_applet',
|
|
|
|
}
|
|
|
|
|
2022-11-02 07:01:52 +00:00
|
|
|
def get_object(self):
|
|
|
|
pk = self.kwargs.get('pk')
|
|
|
|
if not is_uuid(pk):
|
2023-01-16 11:02:09 +00:00
|
|
|
return get_object_or_404(Applet, name=pk)
|
2022-11-02 07:01:52 +00:00
|
|
|
else:
|
2023-01-16 11:02:09 +00:00
|
|
|
return get_object_or_404(Applet, pk=pk)
|
2022-11-02 07:01:52 +00:00
|
|
|
|
2022-11-01 12:37:04 +00:00
|
|
|
def perform_destroy(self, instance):
|
|
|
|
if not instance.name:
|
|
|
|
raise ValidationError('Applet is not null')
|
|
|
|
path = default_storage.path('applets/{}'.format(instance.name))
|
|
|
|
if os.path.exists(path):
|
|
|
|
shutil.rmtree(path)
|
|
|
|
instance.delete()
|
|
|
|
|
|
|
|
|
2022-10-25 04:57:34 +00:00
|
|
|
class AppletPublicationViewSet(viewsets.ModelViewSet):
|
2022-10-28 10:19:44 +00:00
|
|
|
queryset = AppletPublication.objects.all()
|
2022-10-25 04:57:34 +00:00
|
|
|
serializer_class = serializers.AppletPublicationSerializer
|
2022-11-02 11:07:07 +00:00
|
|
|
filterset_fields = ['host', 'applet', 'status']
|
|
|
|
search_fields = ['applet__name', 'applet__display_name', 'host__name']
|