feat: Support playbook, adhoc share

pull/14124/head
wangruidong 2024-09-06 17:41:09 +08:00 committed by Bryan
parent b6f3c23787
commit f55869a449
13 changed files with 128 additions and 34 deletions

View File

@ -69,7 +69,7 @@
"Address": "Address", "Address": "Address",
"AdhocCreate": "Create the command", "AdhocCreate": "Create the command",
"AdhocDetail": "Command details", "AdhocDetail": "Command details",
"AdhocManage": "Command", "AdhocManage": "Script",
"AdhocUpdate": "Update the command", "AdhocUpdate": "Update the command",
"Advanced": "Advanced settings", "Advanced": "Advanced settings",
"AfterChange": "After changes", "AfterChange": "After changes",

View File

@ -69,7 +69,7 @@
"Address": "アドレス", "Address": "アドレス",
"AdhocCreate": "アドホックコマンドを作成", "AdhocCreate": "アドホックコマンドを作成",
"AdhocDetail": "コマンド詳細", "AdhocDetail": "コマンド詳細",
"AdhocManage": "コマンド", "AdhocManage": "スクリプト管理",
"AdhocUpdate": "コマンドを更新", "AdhocUpdate": "コマンドを更新",
"Advanced": "高度な設定", "Advanced": "高度な設定",
"AfterChange": "変更後", "AfterChange": "変更後",

View File

@ -69,7 +69,7 @@
"Address": "地址", "Address": "地址",
"AdhocCreate": "创建命令", "AdhocCreate": "创建命令",
"AdhocDetail": "命令详情", "AdhocDetail": "命令详情",
"AdhocManage": "命令管理", "AdhocManage": "脚本管理",
"AdhocUpdate": "更新命令", "AdhocUpdate": "更新命令",
"Advanced": "高级设置", "Advanced": "高级设置",
"AfterChange": "变更后", "AfterChange": "变更后",

View File

@ -87,7 +87,7 @@
"Addressee": "收件人", "Addressee": "收件人",
"AdhocCreate": "創建命令", "AdhocCreate": "創建命令",
"AdhocDetail": "命令詳情", "AdhocDetail": "命令詳情",
"AdhocManage": "命令管理", "AdhocManage": "腳本管理",
"AdhocUpdate": "更新命令", "AdhocUpdate": "更新命令",
"Admin": "管理員", "Admin": "管理員",
"AdminUser": "特權用戶", "AdminUser": "特權用戶",

View File

@ -1,22 +1,37 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from orgs.mixins.api import OrgBulkModelViewSet from django.db.models import Q
from common.api.generic import JMSBulkModelViewSet
from common.utils.http import is_true
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
from ..const import Scope
from ..models import AdHoc from ..models import AdHoc
from ..serializers import ( from ..serializers import AdHocSerializer
AdHocSerializer
)
__all__ = [ __all__ = [
'AdHocViewSet' 'AdHocViewSet'
] ]
class AdHocViewSet(OrgBulkModelViewSet): class AdHocViewSet(JMSBulkModelViewSet):
queryset = AdHoc.objects.all()
serializer_class = AdHocSerializer serializer_class = AdHocSerializer
permission_classes = (RBACPermission,) permission_classes = (RBACPermission,)
search_fields = ('name', 'comment') search_fields = ('name', 'comment')
model = AdHoc filterset_fields = ['scope', 'creator']
def check_object_permissions(self, request, obj):
if request.method != 'GET' and obj.creator != request.user:
self.permission_denied(
request, message={"detail": "Deleting other people's script is not allowed"}
)
return super().check_object_permissions(request, obj)
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
return queryset.filter(creator=self.request.user) user = self.request.user
if is_true(self.request.query_params.get('only_myself')):
queryset = queryset.filter(creator=user)
else:
queryset = queryset.filter(Q(creator=user) | Q(scope=Scope.public))
return queryset

View File

@ -4,13 +4,16 @@ import zipfile
from django.conf import settings from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation from django.core.exceptions import SuspiciousFileOperation
from django.db.models import Q
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import status from rest_framework import status
from common.api.generic import JMSBulkModelViewSet
from common.exceptions import JMSException from common.exceptions import JMSException
from orgs.mixins.api import OrgBulkModelViewSet from common.utils.http import is_true
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
from ..const import Scope
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
@ -28,11 +31,19 @@ def unzip_playbook(src, dist):
fz.extract(file, dist) fz.extract(file, dist)
class PlaybookViewSet(OrgBulkModelViewSet): class PlaybookViewSet(JMSBulkModelViewSet):
serializer_class = PlaybookSerializer serializer_class = PlaybookSerializer
permission_classes = (RBACPermission,) permission_classes = (RBACPermission,)
model = Playbook queryset = Playbook.objects.all()
search_fields = ('name', 'comment') search_fields = ('name', 'comment')
filterset_fields = ['scope', 'creator']
def check_object_permissions(self, request, obj):
if request.method != 'GET' and obj.creator != request.user:
self.permission_denied(
request, message={"detail": "Deleting other people's playbook is not allowed"}
)
return super().check_object_permissions(request, obj)
def perform_destroy(self, instance): def perform_destroy(self, instance):
if instance.job_set.exists(): if instance.job_set.exists():
@ -45,7 +56,11 @@ class PlaybookViewSet(OrgBulkModelViewSet):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
queryset = queryset.filter(creator=self.request.user) user = self.request.user
if is_true(self.request.query_params.get('only_myself')):
queryset = queryset.filter(creator=user)
else:
queryset = queryset.filter(Q(creator=user) | Q(scope=Scope.public))
return queryset return queryset
def perform_create(self, serializer): def perform_create(self, serializer):
@ -85,7 +100,8 @@ class PlaybookFileBrowserAPIView(APIView):
def get(self, request, **kwargs): def get(self, request, **kwargs):
playbook_id = kwargs.get('pk') playbook_id = kwargs.get('pk')
playbook = self.get_playbook(playbook_id) user = self.request.user
playbook = get_object_or_404(Playbook, Q(creator=user) | Q(scope=Scope.public), id=playbook_id)
work_path = playbook.work_dir work_path = playbook.work_dir
file_key = request.query_params.get('key', '') file_key = request.query_params.get('key', '')
if file_key: if file_key:

View File

@ -1,5 +1,5 @@
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 _, pgettext_lazy
class StrategyChoice(models.TextChoices): class StrategyChoice(models.TextChoices):
@ -80,3 +80,8 @@ class JobStatus(models.TextChoices):
CELERY_LOG_MAGIC_MARK = b'\x00\x00\x00\x00\x00' CELERY_LOG_MAGIC_MARK = b'\x00\x00\x00\x00\x00'
COMMAND_EXECUTION_DISABLED = _('Command execution disabled') COMMAND_EXECUTION_DISABLED = _('Command execution disabled')
class Scope(models.TextChoices):
public = 'public', pgettext_lazy("scope", 'Public')
private = 'private', _('Private')

View File

@ -0,0 +1,41 @@
# Generated by Django 4.1.13 on 2024-09-06 08:32
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('ops', '0002_celerytask'),
]
operations = [
migrations.AlterUniqueTogether(
name='adhoc',
unique_together={('name', 'creator')},
),
migrations.AlterUniqueTogether(
name='playbook',
unique_together={('name', 'creator')},
),
migrations.AddField(
model_name='adhoc',
name='scope',
field=models.CharField(default='public', max_length=64, verbose_name='Scope'),
),
migrations.AddField(
model_name='playbook',
name='scope',
field=models.CharField(default='public', max_length=64, verbose_name='Scope'),
),
migrations.RemoveField(
model_name='adhoc',
name='org_id',
),
migrations.RemoveField(
model_name='playbook',
name='org_id',
),
]

View File

@ -8,14 +8,13 @@ from common.utils import get_logger
__all__ = ["AdHoc"] __all__ = ["AdHoc"]
from ops.const import AdHocModules from common.db.models import JMSBaseModel
from ops.const import AdHocModules, Scope
from orgs.mixins.models import JMSOrgBaseModel
logger = get_logger(__file__) logger = get_logger(__file__)
class AdHoc(JMSOrgBaseModel): class AdHoc(JMSBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all') pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all')
@ -24,6 +23,7 @@ class AdHoc(JMSOrgBaseModel):
args = models.CharField(max_length=8192, default='', verbose_name=_('Args')) args = models.CharField(max_length=8192, default='', verbose_name=_('Args'))
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)
scope = models.CharField(max_length=64, default=Scope.public, verbose_name=_('Scope'))
@property @property
def row_count(self): def row_count(self):
@ -40,5 +40,5 @@ class AdHoc(JMSOrgBaseModel):
return "{}: {}".format(self.module, self.args) return "{}: {}".format(self.module, self.args)
class Meta: class Meta:
unique_together = [('name', 'org_id', 'creator')] unique_together = [('name', 'creator')]
verbose_name = _("Adhoc") verbose_name = _("Adhoc")

View File

@ -6,9 +6,9 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from private_storage.fields import PrivateFileField from private_storage.fields import PrivateFileField
from ops.const import CreateMethods from common.db.models import JMSBaseModel
from ops.const import CreateMethods, Scope
from ops.exception import PlaybookNoValidEntry from ops.exception import PlaybookNoValidEntry
from orgs.mixins.models import JMSOrgBaseModel
dangerous_keywords = ( dangerous_keywords = (
'hosts:localhost', 'hosts:localhost',
@ -23,7 +23,9 @@ dangerous_keywords = (
) )
class Playbook(JMSOrgBaseModel):
class Playbook(JMSBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'), null=True) name = models.CharField(max_length=128, verbose_name=_('Name'), null=True)
path = PrivateFileField(upload_to='playbooks/') path = PrivateFileField(upload_to='playbooks/')
@ -31,6 +33,7 @@ class Playbook(JMSOrgBaseModel):
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, create_method = models.CharField(max_length=128, choices=CreateMethods.choices, default=CreateMethods.blank,
verbose_name=_('CreateMethod')) verbose_name=_('CreateMethod'))
scope = models.CharField(max_length=64, default=Scope.public, verbose_name=_('Scope'))
vcs_url = models.CharField(max_length=1024, default='', verbose_name=_('VCS URL'), null=True, blank=True) vcs_url = models.CharField(max_length=1024, default='', verbose_name=_('VCS URL'), null=True, blank=True)
def __str__(self): def __str__(self):
@ -84,6 +87,6 @@ class Playbook(JMSOrgBaseModel):
return work_dir return work_dir
class Meta: class Meta:
unique_together = [('name', 'org_id', 'creator')] unique_together = [('name', 'creator')]
verbose_name = _("Playbook") verbose_name = _("Playbook")
ordering = ['date_created'] ordering = ['date_created']

View File

@ -1,17 +1,19 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.serializers.fields import ReadableHiddenField from common.serializers.fields import ReadableHiddenField, LabeledChoiceField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.serializers.mixin import CommonBulkModelSerializer
from .mixin import ScopeSerializerMixin
from ..const import Scope
from ..models import AdHoc from ..models import AdHoc
class AdHocSerializer(BulkOrgResourceModelSerializer): class AdHocSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
class Meta: class Meta:
model = AdHoc model = AdHoc
read_only_field = ["id", "creator", "date_created", "date_updated"] read_only_field = ["id", "creator", "date_created", "date_updated"]
fields = read_only_field + ["id", "name", "module", "args", "comment"] fields = read_only_field + ["id", "name", "scope", "module", "args", "comment"]

View File

@ -0,0 +1,11 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from common.serializers.fields import LabeledChoiceField
from ..const import Scope
class ScopeSerializerMixin(serializers.Serializer):
scope = LabeledChoiceField(
choices=Scope.choices, default=Scope.public, label=_("Scope")
)

View File

@ -3,8 +3,9 @@ import os
from rest_framework import serializers from rest_framework import serializers
from common.serializers.fields import ReadableHiddenField from common.serializers.fields import ReadableHiddenField
from common.serializers.mixin import CommonBulkModelSerializer
from ops.models import Playbook from ops.models import Playbook
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from .mixin import ScopeSerializerMixin
def parse_playbook_name(path): def parse_playbook_name(path):
@ -12,7 +13,7 @@ def parse_playbook_name(path):
return file_name.split(".")[-2] return file_name.split(".")[-2]
class PlaybookSerializer(BulkOrgResourceModelSerializer): class PlaybookSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
path = serializers.FileField(required=False) path = serializers.FileField(required=False)
@ -26,6 +27,6 @@ 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', 'scope', "name", "comment", "creator",
'create_method', 'vcs_url', 'create_method', 'vcs_url',
] ]