mirror of https://github.com/jumpserver/jumpserver
playbook ide
parent
7b95859015
commit
9d898f0aec
|
@ -1,13 +1,19 @@
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from ..exception import PlaybookNoValidEntry
|
from ..exception import PlaybookNoValidEntry
|
||||||
from ..models import Playbook
|
from ..models import Playbook
|
||||||
from ..serializers.playbook import PlaybookSerializer
|
from ..serializers.playbook import PlaybookSerializer
|
||||||
|
|
||||||
__all__ = ["PlaybookViewSet"]
|
__all__ = ["PlaybookViewSet", "PlaybookFileBrowserAPIView"]
|
||||||
|
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
def unzip_playbook(src, dist):
|
def unzip_playbook(src, dist):
|
||||||
|
@ -31,6 +37,13 @@ class PlaybookViewSet(OrgBulkModelViewSet):
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
instance = serializer.save()
|
instance = serializer.save()
|
||||||
|
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')
|
||||||
|
|
||||||
|
if instance.create_method == 'upload':
|
||||||
src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name)
|
src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name)
|
||||||
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
|
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
|
||||||
unzip_playbook(src_path, dest_path)
|
unzip_playbook(src_path, dest_path)
|
||||||
|
@ -40,3 +53,159 @@ class PlaybookViewSet(OrgBulkModelViewSet):
|
||||||
return
|
return
|
||||||
os.remove(dest_path)
|
os.remove(dest_path)
|
||||||
raise PlaybookNoValidEntry
|
raise PlaybookNoValidEntry
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybookFileBrowserAPIView(APIView):
|
||||||
|
rbac_perms = ()
|
||||||
|
permission_classes = ()
|
||||||
|
|
||||||
|
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:
|
||||||
|
content = f.read()
|
||||||
|
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', '')
|
||||||
|
|
||||||
|
if is_directory:
|
||||||
|
new_file_path = os.path.join(full_path, 'new_dir')
|
||||||
|
i = 0
|
||||||
|
while os.path.exists(new_file_path):
|
||||||
|
i += 1
|
||||||
|
new_file_path = os.path.join(full_path, 'new_dir({})'.format(i))
|
||||||
|
os.makedirs(new_file_path)
|
||||||
|
else:
|
||||||
|
new_file_path = os.path.join(full_path, 'new_file.yml')
|
||||||
|
i = 0
|
||||||
|
while os.path.exists(new_file_path):
|
||||||
|
i += 1
|
||||||
|
new_file_path = os.path.join(full_path, 'new_file({}).yml'.format(i))
|
||||||
|
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', '')
|
||||||
|
if os.path.dirname(file_key) == 'root':
|
||||||
|
file_key = os.path.basename(file_key)
|
||||||
|
|
||||||
|
new_name = request.data.get('new_name', '')
|
||||||
|
content = request.data.get('content', '')
|
||||||
|
is_directory = request.data.get('is_directory', False)
|
||||||
|
|
||||||
|
if not file_key or file_key == 'root':
|
||||||
|
return Response(status=400)
|
||||||
|
file_path = os.path.join(work_path, file_key)
|
||||||
|
|
||||||
|
if new_name:
|
||||||
|
new_file_path = os.path.join(os.path.dirname(file_path), new_name)
|
||||||
|
os.rename(file_path, new_file_path)
|
||||||
|
file_path = new_file_path
|
||||||
|
|
||||||
|
if not is_directory and content:
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
return Response({'msg': 'ok'})
|
||||||
|
|
||||||
|
def delete(self, request, **kwargs):
|
||||||
|
not_delete_allowed = ['root', 'main.yml']
|
||||||
|
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(status=400)
|
||||||
|
if file_key in not_delete_allowed:
|
||||||
|
return Response(status=400)
|
||||||
|
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": False,
|
||||||
|
"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
|
||||||
|
|
|
@ -29,6 +29,11 @@ DEFAULT_PASSWORD_RULES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateMethods(models.TextChoices):
|
||||||
|
blank = 'blank', _('Blank')
|
||||||
|
vcs = 'vcs', _('VCS')
|
||||||
|
|
||||||
|
|
||||||
class Types(models.TextChoices):
|
class Types(models.TextChoices):
|
||||||
adhoc = 'adhoc', _('Adhoc')
|
adhoc = 'adhoc', _('Adhoc')
|
||||||
playbook = 'playbook', _('Playbook')
|
playbook = 'playbook', _('Playbook')
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.14 on 2023-01-17 03:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ops', '0024_alter_celerytask_date_last_publish'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='playbook',
|
||||||
|
name='create_method',
|
||||||
|
field=models.CharField(choices=[('blank', 'Blank'), ('upload', 'Upload'), ('vcs', 'VCS')], default='blank', max_length=128, verbose_name='CreateMethod'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='playbook',
|
||||||
|
name='vcs_url',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='VCS URL'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -5,6 +5,7 @@ from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from ops.const import CreateMethods
|
||||||
from ops.exception import PlaybookNoValidEntry
|
from ops.exception import PlaybookNoValidEntry
|
||||||
from orgs.mixins.models import JMSOrgBaseModel
|
from orgs.mixins.models import JMSOrgBaseModel
|
||||||
|
|
||||||
|
@ -15,12 +16,20 @@ class Playbook(JMSOrgBaseModel):
|
||||||
path = models.FileField(upload_to='playbooks/')
|
path = models.FileField(upload_to='playbooks/')
|
||||||
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
|
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
|
||||||
comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True)
|
comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True)
|
||||||
|
create_method = models.CharField(max_length=128, choices=CreateMethods.choices, default=CreateMethods.blank,
|
||||||
|
verbose_name=_('CreateMethod'))
|
||||||
|
vcs_url = models.CharField(max_length=1024, default='', verbose_name=_('VCS URL'), null=True, blank=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry(self):
|
def entry(self):
|
||||||
work_dir = os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__())
|
work_dir = self.work_dir
|
||||||
valid_entry = ('main.yml', 'main.yaml', 'main')
|
valid_entry = ('main.yml', 'main.yaml', 'main')
|
||||||
for f in os.listdir(work_dir):
|
for f in os.listdir(work_dir):
|
||||||
if f in valid_entry:
|
if f in valid_entry:
|
||||||
return os.path.join(work_dir, f)
|
return os.path.join(work_dir, f)
|
||||||
raise PlaybookNoValidEntry
|
raise PlaybookNoValidEntry
|
||||||
|
|
||||||
|
@property
|
||||||
|
def work_dir(self):
|
||||||
|
work_dir = os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__())
|
||||||
|
return work_dir
|
||||||
|
|
|
@ -27,5 +27,5 @@ class PlaybookSerializer(BulkOrgResourceModelSerializer):
|
||||||
model = Playbook
|
model = Playbook
|
||||||
read_only_fields = ["id", "date_created", "date_updated"]
|
read_only_fields = ["id", "date_created", "date_updated"]
|
||||||
fields = read_only_fields + [
|
fields = read_only_fields + [
|
||||||
"id", 'path', "name", "comment", "creator",
|
"id", 'path', "name", "comment", "creator", 'create_method', 'vcs_url',
|
||||||
]
|
]
|
||||||
|
|
|
@ -23,6 +23,7 @@ router.register(r'tasks', api.CeleryTaskViewSet, 'task')
|
||||||
router.register(r'task-executions', api.CeleryTaskExecutionViewSet, 'task-executions')
|
router.register(r'task-executions', api.CeleryTaskExecutionViewSet, 'task-executions')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path('playbook/<uuid:pk>/file/', api.PlaybookFileBrowserAPIView.as_view(), name='playbook-file'),
|
||||||
path('variables/help/', api.JobRunVariableHelpAPIView.as_view(), name='variable-help'),
|
path('variables/help/', api.JobRunVariableHelpAPIView.as_view(), name='variable-help'),
|
||||||
path('job-execution/asset-detail/', api.JobAssetDetail.as_view(), name='asset-detail'),
|
path('job-execution/asset-detail/', api.JobAssetDetail.as_view(), name='asset-detail'),
|
||||||
path('job-execution/task-detail/<uuid:task_id>/', api.JobExecutionTaskDetail.as_view(), name='task-detail'),
|
path('job-execution/task-detail/<uuid:task_id>/', api.JobExecutionTaskDetail.as_view(), name='task-detail'),
|
||||||
|
|
Loading…
Reference in New Issue