import os import shutil import zipfile from django.conf import settings from django.shortcuts import get_object_or_404 from rest_framework import status from common.exceptions import JMSException from orgs.mixins.api import OrgBulkModelViewSet from rbac.permissions import RBACPermission from ..exception import PlaybookNoValidEntry from ..models import Playbook from ..serializers.playbook import PlaybookSerializer from django.utils.translation import ugettext_lazy as _ __all__ = ["PlaybookViewSet", "PlaybookFileBrowserAPIView"] from rest_framework.views import APIView from rest_framework.response import Response def unzip_playbook(src, dist): fz = zipfile.ZipFile(src, 'r') for file in fz.namelist(): fz.extract(file, dist) class PlaybookViewSet(OrgBulkModelViewSet): serializer_class = PlaybookSerializer permission_classes = (RBACPermission,) model = Playbook search_fields = ('name', 'comment') def perform_destroy(self, instance): instance = self.get_object() if instance.job_set.exists(): raise JMSException(code='playbook_has_job', detail={"msg": _("Currently playbook is being used in a job")}) instance_id = instance.id super().perform_destroy(instance) dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance_id.__str__()) shutil.rmtree(dest_path) def get_queryset(self): queryset = super().get_queryset() queryset = queryset.filter(creator=self.request.user) return queryset def perform_create(self, serializer): instance = serializer.save() if 'multipart/form-data' in self.request.headers['Content-Type']: src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name) dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__()) try: unzip_playbook(src_path, dest_path) except RuntimeError as e: raise JMSException(code='invalid_playbook_file', detail={"msg": "Unzip failed"}) if 'main.yml' not in os.listdir(dest_path): raise PlaybookNoValidEntry else: if instance.create_method == 'blank': dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__()) os.makedirs(dest_path) with open(os.path.join(dest_path, 'main.yml'), 'w') as f: f.write('## write your playbook here') class PlaybookFileBrowserAPIView(APIView): rbac_perms = () permission_classes = (RBACPermission,) rbac_perms = { 'GET': 'ops.change_playbook', 'POST': 'ops.change_playbook', 'DELETE': 'ops.change_playbook', 'PATCH': 'ops.change_playbook', } protected_files = ['root', 'main.yml'] def get(self, request, **kwargs): playbook_id = kwargs.get('pk') playbook = get_object_or_404(Playbook, id=playbook_id) work_path = playbook.work_dir file_key = request.query_params.get('key', '') if file_key: file_path = os.path.join(work_path, file_key) with open(file_path, 'r') as f: try: content = f.read() except UnicodeDecodeError: content = _('Unsupported file content') return Response({'content': content}) else: expand_key = request.query_params.get('expand', '') nodes = self.generate_tree(playbook, work_path, expand_key) return Response(nodes) def post(self, request, **kwargs): playbook_id = kwargs.get('pk') playbook = get_object_or_404(Playbook, id=playbook_id) work_path = playbook.work_dir parent_key = request.data.get('key', '') if parent_key == 'root': parent_key = '' if os.path.dirname(parent_key) == 'root': parent_key = os.path.basename(parent_key) full_path = os.path.join(work_path, parent_key) is_directory = request.data.get('is_directory', False) content = request.data.get('content', '') name = request.data.get('name', '') def find_new_name(p, is_file=False): if not p: if is_file: p = 'new_file.yml' else: p = 'new_dir' np = os.path.join(full_path, p) n = 0 while os.path.exists(np): n += 1 np = os.path.join(full_path, '{}({})'.format(p, n)) return np if is_directory: new_file_path = find_new_name(name) os.makedirs(new_file_path) else: new_file_path = find_new_name(name, True) with open(new_file_path, 'w') as f: f.write(content) relative_path = os.path.relpath(os.path.dirname(new_file_path), work_path) new_node = { "name": os.path.basename(new_file_path), "title": os.path.basename(new_file_path), "id": os.path.join(relative_path, os.path.basename(new_file_path)) if not os.path.join(relative_path, os.path.basename(new_file_path)).startswith('.') else os.path.basename(new_file_path), "isParent": is_directory, "pId": relative_path if not relative_path.startswith('.') else 'root', "open": True, } if not is_directory: new_node['iconSkin'] = 'file' return Response(new_node) def patch(self, request, **kwargs): playbook_id = kwargs.get('pk') playbook = get_object_or_404(Playbook, id=playbook_id) work_path = playbook.work_dir file_key = request.data.get('key', '') new_name = request.data.get('new_name', '') if file_key in self.protected_files and new_name: return Response({'msg': '{} can not be rename'.format(file_key)}, status=status.HTTP_400_BAD_REQUEST) if os.path.dirname(file_key) == 'root': file_key = os.path.basename(file_key) content = request.data.get('content', '') is_directory = request.data.get('is_directory', False) if not file_key or file_key == 'root': return Response(status=status.HTTP_400_BAD_REQUEST) file_path = os.path.join(work_path, file_key) # rename if new_name: new_file_path = os.path.join(os.path.dirname(file_path), new_name) if new_file_path == file_path: return Response(status=status.HTTP_200_OK) if os.path.exists(new_file_path): return Response({'msg': '{} already exists'.format(new_name)}, status=status.HTTP_400_BAD_REQUEST) os.rename(file_path, new_file_path) # edit content else: if not is_directory: with open(file_path, 'w') as f: f.write(content) return Response(status=status.HTTP_200_OK) def delete(self, request, **kwargs): playbook_id = kwargs.get('pk') playbook = get_object_or_404(Playbook, id=playbook_id) work_path = playbook.work_dir file_key = request.query_params.get('key', '') if not file_key: return Response({'msg': 'key is required'}, status=status.HTTP_400_BAD_REQUEST) if file_key in self.protected_files: return Response({'msg': ' {} can not be delete'.format(file_key)}, status=status.HTTP_400_BAD_REQUEST) file_path = os.path.join(work_path, file_key) if os.path.isdir(file_path): shutil.rmtree(file_path) else: os.remove(file_path) return Response({'msg': 'ok'}) @staticmethod def generate_tree(playbook, root_path, expand_key=None): nodes = [{ "name": playbook.name, "title": playbook.name, "id": 'root', "isParent": True, "open": True, "pId": '', "temp": False }] for path, dirs, files in os.walk(root_path): dirs.sort() files.sort() relative_path = os.path.relpath(path, root_path) for d in dirs: node = { "name": d, "title": d, "id": os.path.join(relative_path, d) if not os.path.join(relative_path, d).startswith( '.') else d, "isParent": True, "open": True, "pId": relative_path if not relative_path.startswith('.') else 'root', "temp": False } if expand_key == node['id']: node['open'] = True nodes.append(node) for f in files: node = { "name": f, "title": f, "iconSkin": 'file', "id": os.path.join(relative_path, f) if not os.path.join(relative_path, f).startswith( '.') else f, "isParent": False, "open": False, "pId": relative_path if not relative_path.startswith('.') else 'root', "temp": False } nodes.append(node) return nodes