playbook ide

pull/9399/head
Aaron3S 2023-01-18 18:03:33 +08:00 committed by Jiangjie.Bai
parent 7b95859015
commit 9d898f0aec
6 changed files with 219 additions and 12 deletions

View File

@ -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,12 +37,175 @@ class PlaybookViewSet(OrgBulkModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
instance = serializer.save() instance = serializer.save()
src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name) if instance.create_method == 'blank':
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) os.makedirs(dest_path)
valid_entry = ('main.yml', 'main.yaml', 'main') with open(os.path.join(dest_path, 'main.yml'), 'w') as f:
for f in os.listdir(dest_path): f.write('## write your playbook here')
if f in valid_entry:
return if instance.create_method == 'upload':
os.remove(dest_path) src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name)
raise PlaybookNoValidEntry dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
unzip_playbook(src_path, dest_path)
valid_entry = ('main.yml', 'main.yaml', 'main')
for f in os.listdir(dest_path):
if f in valid_entry:
return
os.remove(dest_path)
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

View File

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

View File

@ -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'),
),
]

View File

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

View File

@ -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',
] ]

View File

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