mirror of https://github.com/jumpserver/jumpserver
perf: 修改 ansible 表结构
parent
df5e63b3be
commit
0fb4b52232
|
@ -4,15 +4,14 @@ from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.const.choices import Trigger
|
from common.const.choices import Trigger
|
||||||
from common.mixins.models import CommonModelMixin
|
|
||||||
from common.db.fields import EncryptJsonDictTextField
|
from common.db.fields import EncryptJsonDictTextField
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
|
||||||
from ops.mixin import PeriodTaskModelMixin
|
from ops.mixin import PeriodTaskModelMixin
|
||||||
from ops.tasks import execute_automation_strategy
|
from ops.tasks import execute_automation_strategy
|
||||||
from ops.task_handlers import ExecutionManager
|
from ops.task_handlers import ExecutionManager
|
||||||
|
|
||||||
|
|
||||||
class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
|
class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin):
|
||||||
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
|
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
|
||||||
nodes = models.ManyToManyField(
|
nodes = models.ManyToManyField(
|
||||||
'assets.Node', related_name='automation_strategy', blank=True, verbose_name=_("Nodes")
|
'assets.Node', related_name='automation_strategy', blank=True, verbose_name=_("Nodes")
|
||||||
|
@ -67,7 +66,7 @@ class AutomationStrategyExecution(OrgModelMixin):
|
||||||
default=dict, blank=True, null=True, verbose_name=_('Automation snapshot')
|
default=dict, blank=True, null=True, verbose_name=_('Automation snapshot')
|
||||||
)
|
)
|
||||||
strategy = models.ForeignKey(
|
strategy = models.ForeignKey(
|
||||||
'assets.models.automation.base.BaseAutomation', related_name='execution', on_delete=models.CASCADE,
|
'BaseAutomation', related_name='execution', on_delete=models.CASCADE,
|
||||||
verbose_name=_('Automation strategy')
|
verbose_name=_('Automation strategy')
|
||||||
)
|
)
|
||||||
trigger = models.CharField(
|
trigger = models.CharField(
|
||||||
|
|
|
@ -14,16 +14,12 @@ class Label(OrgModelMixin):
|
||||||
("S", _("System")),
|
("S", _("System")),
|
||||||
("U", _("User"))
|
("U", _("User"))
|
||||||
)
|
)
|
||||||
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"))
|
||||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||||
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
|
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
|
||||||
default=USER_CATEGORY, verbose_name=_("Category"))
|
default=USER_CATEGORY, verbose_name=_("Category"))
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||||
date_created = models.DateTimeField(
|
|
||||||
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_queryset_group_by_name(cls):
|
def get_queryset_group_by_name(cls):
|
||||||
|
|
|
@ -11,16 +11,14 @@ from common.drf.filters import DatetimeRangeFilter
|
||||||
from common.api import CommonGenericViewSet
|
from common.api import CommonGenericViewSet
|
||||||
from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin
|
from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from ops.models import CommandExecution
|
# from ops.models import CommandExecution
|
||||||
from . import filters
|
from . import filters
|
||||||
from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog
|
from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog
|
||||||
from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecutionSerializer
|
from .serializers import FTPLogSerializer, UserLoginLogSerializer
|
||||||
from .serializers import OperateLogSerializer, PasswordChangeLogSerializer, CommandExecutionHostsRelationSerializer
|
from .serializers import OperateLogSerializer, PasswordChangeLogSerializer
|
||||||
|
|
||||||
|
|
||||||
class FTPLogViewSet(CreateModelMixin,
|
class FTPLogViewSet(CreateModelMixin, ListModelMixin, OrgGenericViewSet):
|
||||||
ListModelMixin,
|
|
||||||
OrgGenericViewSet):
|
|
||||||
model = FTPLog
|
model = FTPLog
|
||||||
serializer_class = FTPLogSerializer
|
serializer_class = FTPLogSerializer
|
||||||
extra_filter_backends = [DatetimeRangeFilter]
|
extra_filter_backends = [DatetimeRangeFilter]
|
||||||
|
@ -98,53 +96,53 @@ class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet):
|
||||||
)
|
)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
# Todo: 看看怎么搞
|
||||||
class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
|
# class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
|
||||||
model = CommandExecution
|
# model = CommandExecution
|
||||||
serializer_class = CommandExecutionSerializer
|
# serializer_class = CommandExecutionSerializer
|
||||||
extra_filter_backends = [DatetimeRangeFilter]
|
# extra_filter_backends = [DatetimeRangeFilter]
|
||||||
date_range_filter_fields = [
|
# date_range_filter_fields = [
|
||||||
('date_start', ('date_from', 'date_to'))
|
# ('date_start', ('date_from', 'date_to'))
|
||||||
]
|
# ]
|
||||||
filterset_fields = [
|
# filterset_fields = [
|
||||||
'user__name', 'user__username', 'command',
|
# 'user__name', 'user__username', 'command',
|
||||||
'account', 'is_finished'
|
# 'account', 'is_finished'
|
||||||
]
|
# ]
|
||||||
search_fields = [
|
# search_fields = [
|
||||||
'command', 'user__name', 'user__username',
|
# 'command', 'user__name', 'user__username',
|
||||||
'account__username',
|
# 'account__username',
|
||||||
]
|
# ]
|
||||||
ordering = ['-date_created']
|
# ordering = ['-date_created']
|
||||||
|
#
|
||||||
def get_queryset(self):
|
# def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
# queryset = super().get_queryset()
|
||||||
if getattr(self, 'swagger_fake_view', False):
|
# if getattr(self, 'swagger_fake_view', False):
|
||||||
return queryset.model.objects.none()
|
# return queryset.model.objects.none()
|
||||||
if current_org.is_root():
|
# if current_org.is_root():
|
||||||
return queryset
|
# return queryset
|
||||||
# queryset = queryset.filter(run_as__org_id=current_org.org_id())
|
# # queryset = queryset.filter(run_as__org_id=current_org.org_id())
|
||||||
return queryset
|
# return queryset
|
||||||
|
#
|
||||||
|
#
|
||||||
class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
|
# class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
|
||||||
serializer_class = CommandExecutionHostsRelationSerializer
|
# serializer_class = CommandExecutionHostsRelationSerializer
|
||||||
m2m_field = CommandExecution.hosts.field
|
# m2m_field = CommandExecution.hosts.field
|
||||||
filterset_fields = [
|
# filterset_fields = [
|
||||||
'id', 'asset', 'commandexecution'
|
# 'id', 'asset', 'commandexecution'
|
||||||
]
|
# ]
|
||||||
search_fields = ('asset__name', )
|
# search_fields = ('asset__name', )
|
||||||
http_method_names = ['options', 'get']
|
# http_method_names = ['options', 'get']
|
||||||
rbac_perms = {
|
# rbac_perms = {
|
||||||
'GET': 'ops.view_commandexecution',
|
# 'GET': 'ops.view_commandexecution',
|
||||||
'list': 'ops.view_commandexecution',
|
# 'list': 'ops.view_commandexecution',
|
||||||
}
|
# }
|
||||||
|
#
|
||||||
def get_queryset(self):
|
# def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
# queryset = super().get_queryset()
|
||||||
queryset = queryset.annotate(
|
# queryset = queryset.annotate(
|
||||||
asset_display=Concat(
|
# asset_display=Concat(
|
||||||
F('asset__name'), Value('('),
|
# F('asset__name'), Value('('),
|
||||||
F('asset__address'), Value(')')
|
# F('asset__address'), Value(')')
|
||||||
)
|
# )
|
||||||
)
|
# )
|
||||||
return queryset
|
# return queryset
|
||||||
|
|
|
@ -5,10 +5,9 @@ from rest_framework import filters
|
||||||
from rest_framework.compat import coreapi, coreschema
|
from rest_framework.compat import coreapi, coreschema
|
||||||
|
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from ops.models import CommandExecution
|
|
||||||
from common.drf.filters import BaseFilterSet
|
from common.drf.filters import BaseFilterSet
|
||||||
|
|
||||||
__all__ = ['CurrentOrgMembersFilter', 'CommandExecutionFilter']
|
__all__ = ['CurrentOrgMembersFilter']
|
||||||
|
|
||||||
|
|
||||||
class CurrentOrgMembersFilter(filters.BaseFilterBackend):
|
class CurrentOrgMembersFilter(filters.BaseFilterBackend):
|
||||||
|
@ -35,21 +34,21 @@ class CurrentOrgMembersFilter(filters.BaseFilterBackend):
|
||||||
queryset = queryset.filter(user__in=self._get_user_list())
|
queryset = queryset.filter(user__in=self._get_user_list())
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
#
|
||||||
class CommandExecutionFilter(BaseFilterSet):
|
# class CommandExecutionFilter(BaseFilterSet):
|
||||||
hostname_ip = CharFilter(method='filter_hostname_ip')
|
# hostname_ip = CharFilter(method='filter_hostname_ip')
|
||||||
|
#
|
||||||
class Meta:
|
# class Meta:
|
||||||
model = CommandExecution.hosts.through
|
# model = CommandExecution.hosts.through
|
||||||
fields = (
|
# fields = (
|
||||||
'id', 'asset', 'commandexecution', 'hostname_ip'
|
# 'id', 'asset', 'commandexecution', 'hostname_ip'
|
||||||
)
|
# )
|
||||||
|
#
|
||||||
def filter_hostname_ip(self, queryset, name, value):
|
# def filter_hostname_ip(self, queryset, name, value):
|
||||||
queryset = queryset.annotate(
|
# queryset = queryset.annotate(
|
||||||
hostname_ip=Concat(
|
# hostname_ip=Concat(
|
||||||
F('asset__hostname'), Value('('),
|
# F('asset__hostname'), Value('('),
|
||||||
F('asset__address'), Value(')')
|
# F('asset__address'), Value(')')
|
||||||
)
|
# )
|
||||||
).filter(hostname_ip__icontains=value)
|
# ).filter(hostname_ip__icontains=value)
|
||||||
return queryset
|
# return queryset
|
||||||
|
|
|
@ -5,7 +5,6 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from common.drf.serializers import BulkSerializerMixin
|
from common.drf.serializers import BulkSerializerMixin
|
||||||
from terminal.models import Session
|
from terminal.models import Session
|
||||||
from ops.models import CommandExecution
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,42 +75,42 @@ class SessionAuditSerializer(serializers.ModelSerializer):
|
||||||
model = Session
|
model = Session
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
#
|
||||||
class CommandExecutionSerializer(serializers.ModelSerializer):
|
# class CommandExecutionSerializer(serializers.ModelSerializer):
|
||||||
is_success = serializers.BooleanField(read_only=True, label=_('Is success'))
|
# is_success = serializers.BooleanField(read_only=True, label=_('Is success'))
|
||||||
hosts_display = serializers.ListSerializer(
|
# hosts_display = serializers.ListSerializer(
|
||||||
child=serializers.CharField(), source='hosts', read_only=True, label=_('Hosts display')
|
# child=serializers.CharField(), source='hosts', read_only=True, label=_('Hosts display')
|
||||||
)
|
# )
|
||||||
|
#
|
||||||
class Meta:
|
# class Meta:
|
||||||
model = CommandExecution
|
# model = CommandExecution
|
||||||
fields_mini = ['id']
|
# fields_mini = ['id']
|
||||||
fields_small = fields_mini + [
|
# fields_small = fields_mini + [
|
||||||
'command', 'is_finished', 'user',
|
# 'command', 'is_finished', 'user',
|
||||||
'date_start', 'result', 'is_success', 'org_id'
|
# 'date_start', 'result', 'is_success', 'org_id'
|
||||||
]
|
# ]
|
||||||
fields = fields_small + ['hosts', 'hosts_display', 'user_display']
|
# fields = fields_small + ['hosts', 'hosts_display', 'user_display']
|
||||||
extra_kwargs = {
|
# extra_kwargs = {
|
||||||
'result': {'label': _('Result')}, # model 上的方法,只能在这修改
|
# 'result': {'label': _('Result')}, # model 上的方法,只能在这修改
|
||||||
'is_success': {'label': _('Is success')},
|
# 'is_success': {'label': _('Is success')},
|
||||||
'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改
|
# 'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改
|
||||||
'user': {'label': _('User')},
|
# 'user': {'label': _('User')},
|
||||||
'user_display': {'label': _('User display')},
|
# 'user_display': {'label': _('User display')},
|
||||||
}
|
# }
|
||||||
|
#
|
||||||
@classmethod
|
# @classmethod
|
||||||
def setup_eager_loading(cls, queryset):
|
# def setup_eager_loading(cls, queryset):
|
||||||
""" Perform necessary eager loading of data. """
|
# """ Perform necessary eager loading of data. """
|
||||||
queryset = queryset.prefetch_related('user', 'hosts')
|
# queryset = queryset.prefetch_related('user', 'hosts')
|
||||||
return queryset
|
# return queryset
|
||||||
|
#
|
||||||
|
#
|
||||||
class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
# class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
asset_display = serializers.ReadOnlyField()
|
# asset_display = serializers.ReadOnlyField()
|
||||||
commandexecution_display = serializers.ReadOnlyField()
|
# commandexecution_display = serializers.ReadOnlyField()
|
||||||
|
#
|
||||||
class Meta:
|
# class Meta:
|
||||||
model = CommandExecution.hosts.through
|
# model = CommandExecution.hosts.through
|
||||||
fields = [
|
# fields = [
|
||||||
'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display'
|
# 'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display'
|
||||||
]
|
# ]
|
||||||
|
|
|
@ -15,8 +15,8 @@ router.register(r'ftp-logs', api.FTPLogViewSet, 'ftp-log')
|
||||||
router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log')
|
router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log')
|
||||||
router.register(r'operate-logs', api.OperateLogViewSet, 'operate-log')
|
router.register(r'operate-logs', api.OperateLogViewSet, 'operate-log')
|
||||||
router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log')
|
router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log')
|
||||||
router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log')
|
# router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log')
|
||||||
router.register(r'command-executions-hosts-relations', api.CommandExecutionHostRelationViewSet, 'command-executions-hosts-relation')
|
# router.register(r'command-executions-hosts-relations', api.CommandExecutionHostRelationViewSet, 'command-executions-hosts-relation')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
|
@ -16,6 +16,7 @@ VERSION = const.VERSION
|
||||||
BASE_DIR = const.BASE_DIR
|
BASE_DIR = const.BASE_DIR
|
||||||
PROJECT_DIR = const.PROJECT_DIR
|
PROJECT_DIR = const.PROJECT_DIR
|
||||||
DATA_DIR = os.path.join(PROJECT_DIR, 'data')
|
DATA_DIR = os.path.join(PROJECT_DIR, 'data')
|
||||||
|
ANSIBLE_DIR = os.path.join(DATA_DIR, 'ansible')
|
||||||
CERTS_DIR = os.path.join(DATA_DIR, 'certs')
|
CERTS_DIR = os.path.join(DATA_DIR, 'certs')
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
|
|
|
@ -15,7 +15,7 @@ class DefaultCallback:
|
||||||
dark={},
|
dark={},
|
||||||
skipped=[],
|
skipped=[],
|
||||||
)
|
)
|
||||||
self.status = 'starting'
|
self.status = 'running'
|
||||||
self.finished = False
|
self.finished = False
|
||||||
|
|
||||||
def is_success(self):
|
def is_success(self):
|
||||||
|
|
|
@ -3,21 +3,19 @@ from collections import defaultdict
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ['JMSInventory']
|
||||||
'JMSInventory',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class JMSInventory:
|
class JMSInventory:
|
||||||
def __init__(self, assets, account_username=None, account_policy='smart', host_var_callback=None):
|
def __init__(self, assets, account='', account_policy='smart', host_var_callback=None):
|
||||||
"""
|
"""
|
||||||
:param assets:
|
:param assets:
|
||||||
:param account_username: account username name if not set use account_policy
|
:param account: account username name if not set use account_policy
|
||||||
:param account_policy:
|
:param account_policy:
|
||||||
:param host_var_callback:
|
:param host_var_callback:
|
||||||
"""
|
"""
|
||||||
self.assets = self.clean_assets(assets)
|
self.assets = self.clean_assets(assets)
|
||||||
self.account_username = account_username
|
self.account_username = account
|
||||||
self.account_policy = account_policy
|
self.account_policy = account_policy
|
||||||
self.host_var_callback = host_var_callback
|
self.host_var_callback = host_var_callback
|
||||||
|
|
||||||
|
|
|
@ -68,3 +68,11 @@ class PlaybookRunner:
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
return self.cb
|
return self.cb
|
||||||
|
|
||||||
|
|
||||||
|
class CommandRunner(AdHocRunner):
|
||||||
|
def __init__(self, inventory, command, pattern='*', project_dir='/tmp/'):
|
||||||
|
super().__init__(inventory, 'shell', command, pattern, project_dir)
|
||||||
|
|
||||||
|
def run(self, verbosity=0, **kwargs):
|
||||||
|
return super().run(verbosity, **kwargs)
|
||||||
|
|
|
@ -2,4 +2,3 @@
|
||||||
#
|
#
|
||||||
from .adhoc import *
|
from .adhoc import *
|
||||||
from .celery import *
|
from .celery import *
|
||||||
from .command import *
|
|
||||||
|
|
|
@ -6,52 +6,18 @@ from rest_framework import viewsets, generics
|
||||||
from rest_framework.views import Response
|
from rest_framework.views import Response
|
||||||
|
|
||||||
from common.drf.serializers import CeleryTaskSerializer
|
from common.drf.serializers import CeleryTaskSerializer
|
||||||
from ..models import Task, AdHoc, AdHocExecution
|
from ..models import AdHoc, AdHocExecution
|
||||||
from ..serializers import (
|
from ..serializers import (
|
||||||
TaskSerializer,
|
|
||||||
AdHocSerializer,
|
AdHocSerializer,
|
||||||
AdHocExecutionSerializer,
|
AdHocExecutionSerializer,
|
||||||
TaskDetailSerializer,
|
|
||||||
AdHocDetailSerializer,
|
AdHocDetailSerializer,
|
||||||
)
|
)
|
||||||
from ..tasks import run_ansible_task
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'TaskViewSet', 'TaskRun', 'AdHocViewSet', 'AdHocRunHistoryViewSet'
|
'AdHocViewSet', 'AdHocExecutionViewSet'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TaskViewSet(OrgBulkModelViewSet):
|
|
||||||
model = Task
|
|
||||||
filterset_fields = ("name",)
|
|
||||||
search_fields = filterset_fields
|
|
||||||
serializer_class = TaskSerializer
|
|
||||||
|
|
||||||
def get_serializer_class(self):
|
|
||||||
if self.action == 'retrieve':
|
|
||||||
return TaskDetailSerializer
|
|
||||||
return super().get_serializer_class()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = super().get_queryset()
|
|
||||||
queryset = queryset.select_related('latest_execution')
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class TaskRun(generics.RetrieveAPIView):
|
|
||||||
queryset = Task.objects.all()
|
|
||||||
serializer_class = CeleryTaskSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'retrieve': 'ops.add_adhoc'
|
|
||||||
}
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
|
||||||
task = self.get_object()
|
|
||||||
t = run_ansible_task.delay(str(task.id))
|
|
||||||
return Response({"task": t.id})
|
|
||||||
|
|
||||||
|
|
||||||
class AdHocViewSet(viewsets.ModelViewSet):
|
class AdHocViewSet(viewsets.ModelViewSet):
|
||||||
queryset = AdHoc.objects.all()
|
queryset = AdHoc.objects.all()
|
||||||
serializer_class = AdHocSerializer
|
serializer_class = AdHocSerializer
|
||||||
|
@ -61,23 +27,17 @@ class AdHocViewSet(viewsets.ModelViewSet):
|
||||||
return AdHocDetailSerializer
|
return AdHocDetailSerializer
|
||||||
return super().get_serializer_class()
|
return super().get_serializer_class()
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
task_id = self.request.query_params.get('task')
|
|
||||||
if task_id:
|
|
||||||
task = get_object_or_404(Task, id=task_id)
|
|
||||||
self.queryset = self.queryset.filter(task=task)
|
|
||||||
return self.queryset
|
|
||||||
|
|
||||||
|
class AdHocExecutionViewSet(viewsets.ModelViewSet):
|
||||||
class AdHocRunHistoryViewSet(viewsets.ModelViewSet):
|
|
||||||
queryset = AdHocExecution.objects.all()
|
queryset = AdHocExecution.objects.all()
|
||||||
serializer_class = AdHocExecutionSerializer
|
serializer_class = AdHocExecutionSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
task_id = self.request.query_params.get('task')
|
task_id = self.request.query_params.get('task')
|
||||||
adhoc_id = self.request.query_params.get('adhoc')
|
adhoc_id = self.request.query_params.get('adhoc')
|
||||||
|
|
||||||
if task_id:
|
if task_id:
|
||||||
task = get_object_or_404(Task, id=task_id)
|
task = get_object_or_404(AdHoc, id=task_id)
|
||||||
adhocs = task.adhoc.all()
|
adhocs = task.adhoc.all()
|
||||||
self.queryset = self.queryset.filter(adhoc__in=adhocs)
|
self.queryset = self.queryset.filter(adhoc__in=adhocs)
|
||||||
|
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from rest_framework import viewsets
|
|
||||||
from rest_framework.exceptions import ValidationError
|
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from assets.models import Asset, Node
|
|
||||||
from orgs.mixins.api import RootOrgViewMixin
|
|
||||||
from rbac.permissions import RBACPermission
|
|
||||||
from ..models import CommandExecution
|
|
||||||
from ..serializers import CommandExecutionSerializer
|
|
||||||
from ..tasks import run_command_execution
|
|
||||||
|
|
||||||
|
|
||||||
class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet):
|
|
||||||
serializer_class = CommandExecutionSerializer
|
|
||||||
permission_classes = (RBACPermission,)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return CommandExecution.objects.filter(user_id=str(self.request.user.id))
|
|
||||||
|
|
||||||
def check_hosts(self, serializer):
|
|
||||||
data = serializer.validated_data
|
|
||||||
assets = data["hosts"]
|
|
||||||
user = self.request.user
|
|
||||||
|
|
||||||
# TOdo:
|
|
||||||
# Q(granted_by_permissions__system_users__id=system_user.id) &
|
|
||||||
q = (
|
|
||||||
Q(granted_by_permissions__users=user) |
|
|
||||||
Q(granted_by_permissions__user_groups__users=user)
|
|
||||||
)
|
|
||||||
|
|
||||||
permed_assets = set()
|
|
||||||
permed_assets.update(Asset.objects.filter(id__in=[a.id for a in assets]).filter(q).distinct())
|
|
||||||
node_keys = Node.objects.filter(q).distinct().values_list('key', flat=True)
|
|
||||||
|
|
||||||
nodes_assets_q = Q()
|
|
||||||
for _key in node_keys:
|
|
||||||
nodes_assets_q |= Q(nodes__key__startswith=f'{_key}:')
|
|
||||||
nodes_assets_q |= Q(nodes__key=_key)
|
|
||||||
|
|
||||||
permed_assets.update(
|
|
||||||
Asset.objects.filter(
|
|
||||||
id__in=[a.id for a in assets]
|
|
||||||
).filter(
|
|
||||||
nodes_assets_q
|
|
||||||
).distinct()
|
|
||||||
)
|
|
||||||
|
|
||||||
invalid_assets = set(assets) - set(permed_assets)
|
|
||||||
if invalid_assets:
|
|
||||||
msg = _("Not has host {} permission").format(
|
|
||||||
[str(a.id) for a in invalid_assets]
|
|
||||||
)
|
|
||||||
raise ValidationError({"hosts": msg})
|
|
||||||
|
|
||||||
def check_permissions(self, request):
|
|
||||||
if not settings.SECURITY_COMMAND_EXECUTION:
|
|
||||||
return self.permission_denied(request, "Command execution disabled")
|
|
||||||
return super().check_permissions(request)
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
self.check_hosts(serializer)
|
|
||||||
instance = serializer.save()
|
|
||||||
instance.user = self.request.user
|
|
||||||
instance.save()
|
|
||||||
cols = self.request.query_params.get("cols", '80')
|
|
||||||
rows = self.request.query_params.get("rows", '24')
|
|
||||||
transaction.on_commit(lambda: run_command_execution.apply_async(
|
|
||||||
args=(instance.id,), kwargs={"cols": cols, "rows": rows},
|
|
||||||
task_id=str(instance.id)
|
|
||||||
))
|
|
|
@ -1,149 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from .ansible.inventory import BaseInventory
|
|
||||||
|
|
||||||
from common.utils import get_logger
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'JMSInventory', 'JMSCustomInventory',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
class JMSBaseInventory(BaseInventory):
|
|
||||||
def convert_to_ansible(self, asset, run_as_admin=False):
|
|
||||||
info = {
|
|
||||||
'id': asset.id,
|
|
||||||
'name': asset.name,
|
|
||||||
'ip': asset.address,
|
|
||||||
'port': asset.ssh_port,
|
|
||||||
'vars': dict(),
|
|
||||||
'groups': [],
|
|
||||||
}
|
|
||||||
if asset.domain and asset.domain.has_gateway():
|
|
||||||
info["vars"].update(self.make_proxy_command(asset))
|
|
||||||
if run_as_admin:
|
|
||||||
info.update(asset.get_auth_info(with_become=True))
|
|
||||||
if asset.is_windows():
|
|
||||||
info["vars"].update({
|
|
||||||
"ansible_connection": "ssh",
|
|
||||||
"ansible_shell_type": settings.WINDOWS_SSH_DEFAULT_SHELL,
|
|
||||||
})
|
|
||||||
for label in asset.labels.all():
|
|
||||||
info["vars"].update({
|
|
||||||
label.name: label.value
|
|
||||||
})
|
|
||||||
if asset.domain:
|
|
||||||
info["vars"].update({
|
|
||||||
"domain": asset.domain.name,
|
|
||||||
})
|
|
||||||
return info
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_proxy_command(asset):
|
|
||||||
gateway = asset.domain.random_gateway()
|
|
||||||
proxy_command_list = [
|
|
||||||
"ssh", "-o", "Port={}".format(gateway.port),
|
|
||||||
"-o", "StrictHostKeyChecking=no",
|
|
||||||
"{}@{}".format(gateway.username, gateway.address),
|
|
||||||
"-W", "%h:%p", "-q",
|
|
||||||
]
|
|
||||||
|
|
||||||
if gateway.password:
|
|
||||||
proxy_command_list.insert(
|
|
||||||
0, "sshpass -p '{}'".format(gateway.password)
|
|
||||||
)
|
|
||||||
if gateway.private_key:
|
|
||||||
proxy_command_list.append("-i {}".format(gateway.private_key_file))
|
|
||||||
|
|
||||||
proxy_command = "'-o ProxyCommand={}'".format(
|
|
||||||
" ".join(proxy_command_list)
|
|
||||||
)
|
|
||||||
return {"ansible_ssh_common_args": proxy_command}
|
|
||||||
|
|
||||||
|
|
||||||
class JMSInventory(JMSBaseInventory):
|
|
||||||
"""
|
|
||||||
JMS Inventory is the inventory with jumpserver assets, so you can
|
|
||||||
write you own inventory, construct you inventory,
|
|
||||||
user_info is obtained from admin_user or asset_user
|
|
||||||
"""
|
|
||||||
def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None, system_user=None):
|
|
||||||
"""
|
|
||||||
:param assets: assets
|
|
||||||
:param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同
|
|
||||||
:param run_as: 用户名(添加了统一的资产用户管理器之后AssetUserManager加上之后修改为username)
|
|
||||||
:param become_info: 是否become成某个用户去执行
|
|
||||||
"""
|
|
||||||
self.assets = assets
|
|
||||||
self.using_admin = run_as_admin
|
|
||||||
self.run_as = run_as
|
|
||||||
self.system_user = system_user
|
|
||||||
self.become_info = become_info
|
|
||||||
|
|
||||||
host_list = []
|
|
||||||
|
|
||||||
for asset in assets:
|
|
||||||
host = self.convert_to_ansible(asset, run_as_admin=run_as_admin)
|
|
||||||
if run_as is not None:
|
|
||||||
run_user_info = self.get_run_user_info(host)
|
|
||||||
host.update(run_user_info)
|
|
||||||
if become_info and asset.is_unixlike():
|
|
||||||
host.update(become_info)
|
|
||||||
host_list.append(host)
|
|
||||||
|
|
||||||
super().__init__(host_list=host_list)
|
|
||||||
|
|
||||||
def get_run_user_info(self, host):
|
|
||||||
if not self.run_as and not self.system_user:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
asset_id = host.get('id', '')
|
|
||||||
asset = self.assets.filter(id=asset_id).first()
|
|
||||||
if not asset:
|
|
||||||
logger.error('Host not found: ', asset_id)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
if self.system_user:
|
|
||||||
self.system_user.load_asset_special_auth(asset=asset, username=self.run_as)
|
|
||||||
return self.system_user._to_secret_json()
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
class JMSCustomInventory(JMSBaseInventory):
|
|
||||||
"""
|
|
||||||
JMS Custom Inventory is the inventory with jumpserver assets,
|
|
||||||
user_info is obtained from custom parameter
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, assets, username, password=None, public_key=None, private_key=None):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
self.assets = assets
|
|
||||||
self.username = username
|
|
||||||
self.password = password
|
|
||||||
self.public_key = public_key
|
|
||||||
self.private_key = private_key
|
|
||||||
|
|
||||||
host_list = []
|
|
||||||
|
|
||||||
for asset in assets:
|
|
||||||
host = self.convert_to_ansible(asset)
|
|
||||||
run_user_info = self.get_run_user_info()
|
|
||||||
host.update(run_user_info)
|
|
||||||
host_list.append(host)
|
|
||||||
|
|
||||||
super().__init__(host_list=host_list)
|
|
||||||
|
|
||||||
def get_run_user_info(self):
|
|
||||||
return {
|
|
||||||
'username': self.username,
|
|
||||||
'password': self.password,
|
|
||||||
'public_key': self.public_key,
|
|
||||||
'private_key': self.private_key
|
|
||||||
}
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-10-08 07:19
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0106_auto_20220916_1556'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('ops', '0023_auto_20220929_2025'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='adhocexecution',
|
||||||
|
name='adhoc',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='adhocexecution',
|
||||||
|
name='task',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='commandexecution',
|
||||||
|
name='hosts',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='commandexecution',
|
||||||
|
name='user',
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='task',
|
||||||
|
unique_together=None,
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='task',
|
||||||
|
name='latest_adhoc',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='task',
|
||||||
|
name='latest_execution',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AdHoc',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AdHocExecution',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='CommandExecution',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Task',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-10-08 08:31
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0106_auto_20220916_1556'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('ops', '0024_auto_20221008_1514'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdHoc',
|
||||||
|
fields=[
|
||||||
|
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||||
|
('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('is_periodic', models.BooleanField(default=False)),
|
||||||
|
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
|
||||||
|
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
|
||||||
|
('account', models.CharField(default='root', max_length=128, verbose_name='Account')),
|
||||||
|
('account_policy', models.CharField(default='root', max_length=128, verbose_name='Account policy')),
|
||||||
|
('date_last_run', models.DateTimeField(null=True, verbose_name='Date last run')),
|
||||||
|
('pattern', models.CharField(default='all', max_length=1024, verbose_name='Pattern')),
|
||||||
|
('module', models.CharField(default='shell', max_length=128, verbose_name='Module')),
|
||||||
|
('args', models.CharField(default='', max_length=1024, verbose_name='Args')),
|
||||||
|
('assets', models.ManyToManyField(to='assets.Asset', verbose_name='Assets')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdHocExecution',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('status', models.CharField(default='running', max_length=16, verbose_name='Status')),
|
||||||
|
('result', models.JSONField(blank=True, null=True, verbose_name='Result')),
|
||||||
|
('summary', models.JSONField(default=dict, verbose_name='Summary')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
|
||||||
|
('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')),
|
||||||
|
('date_finished', models.DateTimeField(null=True)),
|
||||||
|
('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')),
|
||||||
|
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='ops.adhoc', verbose_name='Adhoc')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'AdHoc execution',
|
||||||
|
'db_table': 'ops_adhoc_execution',
|
||||||
|
'get_latest_by': 'date_start',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adhoc',
|
||||||
|
name='last_execution',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.adhocexecution', verbose_name='Last execution'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adhoc',
|
||||||
|
name='owner',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -14,12 +14,10 @@ from .celery.utils import (
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'PeriodTaskModelMixin', 'PeriodTaskSerializerMixin',
|
'PeriodTaskModelMixin', 'PeriodTaskSerializerMixin',
|
||||||
'PeriodTaskFormMixin',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PeriodTaskModelMixin(models.Model):
|
class PeriodTaskModelMixin(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=128, unique=False, verbose_name=_("Name")
|
max_length=128, unique=False, verbose_name=_("Name")
|
||||||
)
|
)
|
||||||
|
@ -140,42 +138,3 @@ class PeriodTaskSerializerMixin(serializers.Serializer):
|
||||||
msg = _("Require periodic or regularly perform setting")
|
msg = _("Require periodic or regularly perform setting")
|
||||||
raise serializers.ValidationError(msg)
|
raise serializers.ValidationError(msg)
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
|
|
||||||
class PeriodTaskFormMixin(forms.Form):
|
|
||||||
is_periodic = forms.BooleanField(
|
|
||||||
initial=True, required=False, label=_('Periodic perform')
|
|
||||||
)
|
|
||||||
crontab = forms.CharField(
|
|
||||||
max_length=128, required=False, label=_('Regularly perform'),
|
|
||||||
help_text=_("eg: Every Sunday 03:05 run <5 3 * * 0> <br> "
|
|
||||||
"Tips: "
|
|
||||||
"Using 5 digits linux crontab expressions "
|
|
||||||
"<min hour day month week> "
|
|
||||||
"(<a href='https://tool.lu/crontab/' target='_blank'>Online tools</a>) <br>"
|
|
||||||
"Note: "
|
|
||||||
"If both Regularly perform and Cycle perform are set, "
|
|
||||||
"give priority to Regularly perform"),
|
|
||||||
)
|
|
||||||
interval = forms.IntegerField(
|
|
||||||
required=False, initial=24,
|
|
||||||
help_text=_('Unit: hour'), label=_("Cycle perform"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_initial_for_field(self, field, field_name):
|
|
||||||
"""
|
|
||||||
Return initial data for field on form. Use initial data from the form
|
|
||||||
or the field, in that order. Evaluate callable values.
|
|
||||||
"""
|
|
||||||
if field_name not in ['is_periodic', 'crontab', 'interval']:
|
|
||||||
return super().get_initial_for_field(field, field_name)
|
|
||||||
instance = getattr(self, 'instance', None)
|
|
||||||
if instance is None:
|
|
||||||
return super().get_initial_for_field(field, field_name)
|
|
||||||
init_attr_name = field_name + '_initial'
|
|
||||||
value = getattr(self, init_attr_name, None)
|
|
||||||
if value is None:
|
|
||||||
return super().get_initial_for_field(field, field_name)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,4 +3,3 @@
|
||||||
|
|
||||||
from .adhoc import *
|
from .adhoc import *
|
||||||
from .celery import *
|
from .celery import *
|
||||||
from .command import *
|
|
||||||
|
|
|
@ -1,336 +1,40 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
import uuid
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from celery import current_task
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.utils import get_logger, lazyproperty
|
from common.utils import get_logger
|
||||||
from common.utils.translate import translate_value
|
from .base import BaseAnsibleTask, BaseAnsibleExecution
|
||||||
from common.db.fields import (
|
from ..ansible import AdHocRunner
|
||||||
JsonListTextField, JsonDictCharField, EncryptJsonDictCharField,
|
|
||||||
JsonDictTextField,
|
|
||||||
)
|
|
||||||
from orgs.mixins.models import OrgModelMixin
|
|
||||||
from ..ansible import AdHocRunner, AnsibleError
|
|
||||||
from ..inventory import JMSInventory
|
|
||||||
from ..mixin import PeriodTaskModelMixin
|
|
||||||
|
|
||||||
__all__ = ["Task", "AdHoc", "AdHocExecution"]
|
__all__ = ["AdHoc", "AdHocExecution"]
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class Task(PeriodTaskModelMixin, OrgModelMixin):
|
class AdHoc(BaseAnsibleTask):
|
||||||
"""
|
pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all')
|
||||||
This task is different ansible task, Task like 'push system user', 'get asset info' ..
|
module = models.CharField(max_length=128, default='shell', verbose_name=_('Module'))
|
||||||
One task can have some versions of adhoc, run a task only run the latest version adhoc
|
args = models.CharField(max_length=1024, default='', verbose_name=_('Args'))
|
||||||
"""
|
last_execution = models.ForeignKey('AdHocExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
callback = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Callback")) # Callback must be a registered celery task
|
|
||||||
is_deleted = models.BooleanField(default=False)
|
|
||||||
comment = models.TextField(blank=True, verbose_name=_("Comment"))
|
|
||||||
date_created = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_("Date created"))
|
|
||||||
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
|
|
||||||
latest_adhoc = models.ForeignKey('ops.AdHoc', on_delete=models.SET_NULL,
|
|
||||||
null=True, related_name='task_latest')
|
|
||||||
latest_execution = models.ForeignKey('ops.AdHocExecution', on_delete=models.SET_NULL, null=True, related_name='task_latest')
|
|
||||||
total_run_amount = models.IntegerField(default=0)
|
|
||||||
success_run_amount = models.IntegerField(default=0)
|
|
||||||
_ignore_auto_created_by = True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def short_id(self):
|
|
||||||
return str(self.id).split('-')[-1]
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def versions(self):
|
|
||||||
return self.adhoc.all().count()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_success(self):
|
|
||||||
if self.latest_execution:
|
|
||||||
return self.latest_execution.is_success
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def display_name(self):
|
|
||||||
value = translate_value(self.name)
|
|
||||||
return value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def timedelta(self):
|
|
||||||
if self.latest_execution:
|
|
||||||
return self.latest_execution.timedelta
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def date_start(self):
|
|
||||||
if self.latest_execution:
|
|
||||||
return self.latest_execution.date_start
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def assets_amount(self):
|
|
||||||
if self.latest_execution:
|
|
||||||
return self.latest_execution.hosts_amount
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_latest_adhoc(self):
|
|
||||||
if self.latest_adhoc:
|
|
||||||
return self.latest_adhoc
|
|
||||||
try:
|
|
||||||
adhoc = self.adhoc.all().latest()
|
|
||||||
self.latest_adhoc = adhoc
|
|
||||||
self.save()
|
|
||||||
return adhoc
|
|
||||||
except AdHoc.DoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def history_summary(self):
|
|
||||||
total = self.total_run_amount
|
|
||||||
success = self.success_run_amount
|
|
||||||
failed = total - success
|
|
||||||
return {'total': total, 'success': success, 'failed': failed}
|
|
||||||
|
|
||||||
def get_run_execution(self):
|
|
||||||
return self.execution.all()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
latest_adhoc = self.get_latest_adhoc()
|
|
||||||
if latest_adhoc:
|
|
||||||
return latest_adhoc.run()
|
|
||||||
else:
|
|
||||||
return {'error': 'No adhoc'}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def period_key(self):
|
|
||||||
return self.__str__()
|
|
||||||
|
|
||||||
def get_register_task(self):
|
|
||||||
from ..tasks import run_ansible_task
|
|
||||||
name = self.__str__()
|
|
||||||
task = run_ansible_task.name
|
|
||||||
args = (str(self.id),)
|
|
||||||
kwargs = {"callback": self.callback}
|
|
||||||
return name, task, args, kwargs
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name + '@' + str(self.org_id)
|
return "{}: {}".format(self.module, self.args)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = 'ops_task'
|
|
||||||
unique_together = ('name', 'org_id')
|
|
||||||
ordering = ('-date_updated',)
|
|
||||||
verbose_name = _("Task")
|
|
||||||
get_latest_by = 'date_created'
|
|
||||||
permissions = [
|
|
||||||
('view_taskmonitor', _('Can view task monitor'))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class AdHoc(OrgModelMixin):
|
class AdHocExecution(BaseAnsibleExecution):
|
||||||
"""
|
|
||||||
task: A task reference
|
|
||||||
_tasks: [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ]
|
|
||||||
_options: ansible options, more see ops.ansible.runner.Options
|
|
||||||
run_as_admin: if true, then need get every host admin user run it, because every host may be have different admin user, so we choise host level
|
|
||||||
run_as: username(Add the uniform AssetUserManager <AssetUserManager> and change it to username)
|
|
||||||
_become: May be using become [sudo, su] options. {method: "sudo", user: "user", pass: "pass"]
|
|
||||||
pattern: Even if we set _hosts, We only use that to make inventory, We also can set `patter` to run task on match hosts
|
|
||||||
"""
|
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
|
||||||
task = models.ForeignKey(Task, related_name='adhoc', on_delete=models.CASCADE)
|
|
||||||
tasks = JsonListTextField(verbose_name=_('Tasks'))
|
|
||||||
pattern = models.CharField(max_length=64, default='{}', verbose_name=_('Pattern'))
|
|
||||||
options = JsonDictCharField(max_length=1024, default='', verbose_name=_('Options'))
|
|
||||||
hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host"))
|
|
||||||
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
|
|
||||||
run_as = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Username'))
|
|
||||||
become = EncryptJsonDictCharField(max_length=1024, default='', blank=True, null=True, verbose_name=_("Become"))
|
|
||||||
created_by = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Create by'))
|
|
||||||
date_created = models.DateTimeField(auto_now_add=True, db_index=True)
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def run_times(self):
|
|
||||||
return self.execution.count()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inventory(self):
|
|
||||||
if self.become:
|
|
||||||
become_info = {
|
|
||||||
'become': {
|
|
||||||
self.become
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
become_info = None
|
|
||||||
|
|
||||||
inventory = JMSInventory(
|
|
||||||
self.hosts.all(), run_as_admin=self.run_as_admin,
|
|
||||||
run_as=self.run_as, become_info=become_info, system_user=self.run_system_user
|
|
||||||
)
|
|
||||||
return inventory
|
|
||||||
|
|
||||||
@property
|
|
||||||
def become_display(self):
|
|
||||||
if self.become:
|
|
||||||
return self.become.get("user", "")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
celery_task_id = current_task.request.id
|
|
||||||
except AttributeError:
|
|
||||||
celery_task_id = None
|
|
||||||
|
|
||||||
execution = AdHocExecution(
|
|
||||||
celery_task_id=celery_task_id,
|
|
||||||
adhoc=self, task=self.task,
|
|
||||||
task_display=str(self.task)[:128],
|
|
||||||
date_start=timezone.now(),
|
|
||||||
hosts_amount=self.hosts.count(),
|
|
||||||
)
|
|
||||||
execution.save()
|
|
||||||
return execution.start()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def short_id(self):
|
|
||||||
return str(self.id).split('-')[-1]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def latest_execution(self):
|
|
||||||
try:
|
|
||||||
return self.execution.all().latest()
|
|
||||||
except AdHocExecution.DoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def save(self, **kwargs):
|
|
||||||
instance = super().save(**kwargs)
|
|
||||||
self.task.latest_adhoc = instance
|
|
||||||
self.task.save()
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{} of {}".format(self.task.name, self.short_id)
|
|
||||||
|
|
||||||
def same_with(self, other):
|
|
||||||
if not isinstance(other, self.__class__):
|
|
||||||
return False
|
|
||||||
fields_check = []
|
|
||||||
for field in self.__class__._meta.fields:
|
|
||||||
if field.name not in ['id', 'date_created']:
|
|
||||||
fields_check.append(field)
|
|
||||||
for field in fields_check:
|
|
||||||
if getattr(self, field.name) != getattr(other, field.name):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = "ops_adhoc"
|
|
||||||
get_latest_by = 'date_created'
|
|
||||||
verbose_name = _('AdHoc')
|
|
||||||
|
|
||||||
|
|
||||||
class AdHocExecution(OrgModelMixin):
|
|
||||||
"""
|
"""
|
||||||
AdHoc running history.
|
AdHoc running history.
|
||||||
"""
|
"""
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
task = models.ForeignKey('AdHoc', verbose_name=_("Adhoc"), related_name='executions', on_delete=models.CASCADE)
|
||||||
task = models.ForeignKey(Task, related_name='execution', on_delete=models.SET_NULL, null=True)
|
|
||||||
task_display = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Task display"))
|
|
||||||
celery_task_id = models.UUIDField(default=None, null=True)
|
|
||||||
hosts_amount = models.IntegerField(default=0, verbose_name=_("Host amount"))
|
|
||||||
adhoc = models.ForeignKey(AdHoc, related_name='execution', on_delete=models.SET_NULL, null=True)
|
|
||||||
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start time'))
|
|
||||||
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time'))
|
|
||||||
timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
|
|
||||||
is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
|
|
||||||
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
|
|
||||||
result = JsonDictTextField(blank=True, null=True, verbose_name=_('Adhoc raw result'))
|
|
||||||
summary = JsonDictTextField(blank=True, null=True, verbose_name=_('Adhoc result summary'))
|
|
||||||
|
|
||||||
@property
|
def get_runner(self):
|
||||||
def short_id(self):
|
return AdHocRunner(
|
||||||
return str(self.id).split('-')[-1]
|
self.task.inventory, self.task.module, self.task.args,
|
||||||
|
pattern=self.task.pattern, project_dir=self.private_dir
|
||||||
@property
|
|
||||||
def adhoc_short_id(self):
|
|
||||||
return str(self.adhoc_id).split('-')[-1]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def log_path(self):
|
|
||||||
dt = datetime.datetime.now().strftime('%Y-%m-%d')
|
|
||||||
log_dir = os.path.join(settings.PROJECT_DIR, 'data', 'ansible', dt)
|
|
||||||
if not os.path.exists(log_dir):
|
|
||||||
os.makedirs(log_dir)
|
|
||||||
return os.path.join(log_dir, str(self.id) + '.log')
|
|
||||||
|
|
||||||
def start_runner(self):
|
|
||||||
runner = AdHocRunner(self.adhoc.inventory, options=self.adhoc.options)
|
|
||||||
try:
|
|
||||||
result = runner.run(
|
|
||||||
self.adhoc.tasks,
|
|
||||||
self.adhoc.pattern,
|
|
||||||
self.task.name,
|
|
||||||
execution_id=self.id
|
|
||||||
)
|
)
|
||||||
return result.results_raw, result.results_summary
|
|
||||||
except AnsibleError as e:
|
|
||||||
logger.warn("Failed run adhoc {}, {}".format(self.task.name, e))
|
|
||||||
return {}, {}
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.task.latest_execution = self
|
|
||||||
self.task.save()
|
|
||||||
time_start = time.time()
|
|
||||||
summary = {}
|
|
||||||
raw = ''
|
|
||||||
|
|
||||||
try:
|
|
||||||
raw, summary = self.start_runner()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e, exc_info=True)
|
|
||||||
raw = {"dark": {"all": str(e)}, "contacted": []}
|
|
||||||
finally:
|
|
||||||
self.clean_up(summary, time_start)
|
|
||||||
return raw, summary
|
|
||||||
|
|
||||||
def clean_up(self, summary, time_start):
|
|
||||||
is_success = summary.get('success', False)
|
|
||||||
task = Task.objects.get(id=self.task_id)
|
|
||||||
task.total_run_amount = models.F('total_run_amount') + 1
|
|
||||||
if is_success:
|
|
||||||
task.success_run_amount = models.F('success_run_amount') + 1
|
|
||||||
task.save()
|
|
||||||
AdHocExecution.objects.filter(id=self.id).update(
|
|
||||||
is_finished=True,
|
|
||||||
is_success=is_success,
|
|
||||||
date_finished=timezone.now(),
|
|
||||||
timedelta=time.time() - time_start,
|
|
||||||
summary=summary
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def success_hosts(self):
|
|
||||||
return self.summary.get('contacted', [])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def failed_hosts(self):
|
|
||||||
return self.summary.get('dark', {})
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.short_id
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "ops_adhoc_execution"
|
db_table = "ops_adhoc_execution"
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
import os.path
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from orgs.mixins.models import JMSOrgBaseModel
|
||||||
|
from ..ansible.inventory import JMSInventory
|
||||||
|
from ..mixin import PeriodTaskModelMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAnsibleTask(PeriodTaskModelMixin, JMSOrgBaseModel):
|
||||||
|
owner = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
|
||||||
|
assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets"))
|
||||||
|
account = models.CharField(max_length=128, default='root', verbose_name=_('Account'))
|
||||||
|
account_policy = models.CharField(max_length=128, default='root', verbose_name=_('Account policy'))
|
||||||
|
last_execution = models.ForeignKey('BaseAnsibleExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True)
|
||||||
|
date_last_run = models.DateTimeField(null=True, verbose_name=_('Date last run'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inventory(self):
|
||||||
|
inv = JMSInventory(self.assets.all(), self.account, self.account_policy)
|
||||||
|
return inv.generate()
|
||||||
|
|
||||||
|
def get_register_task(self):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAnsibleExecution(models.Model):
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
|
||||||
|
status = models.CharField(max_length=16, verbose_name=_('Status'), default='running')
|
||||||
|
task = models.ForeignKey(BaseAnsibleTask, on_delete=models.CASCADE, null=True)
|
||||||
|
result = models.JSONField(blank=True, null=True, verbose_name=_('Result'))
|
||||||
|
summary = models.JSONField(default=dict, verbose_name=_('Summary'))
|
||||||
|
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
|
||||||
|
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||||
|
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True)
|
||||||
|
date_finished = models.DateTimeField(null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
ordering = ["-date_start"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.id)
|
||||||
|
|
||||||
|
def private_dir(self):
|
||||||
|
uniq = self.date_created.strftime('%Y%m%d_%H%M%S') + '_' + self.short_id
|
||||||
|
return os.path.join(settings.ANSIBLE_DIR, self.task.name, uniq)
|
||||||
|
|
||||||
|
def get_runner(self):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def update_task(self):
|
||||||
|
self.task.last_execution = self
|
||||||
|
self.task.date_last_run = timezone.now()
|
||||||
|
self.task.save(update_fields=['last_execution', 'date_last_run'])
|
||||||
|
|
||||||
|
def start(self, **kwargs):
|
||||||
|
runner = self.get_runner()
|
||||||
|
try:
|
||||||
|
cb = runner.run(**kwargs)
|
||||||
|
self.status = cb.status
|
||||||
|
self.summary = cb.summary
|
||||||
|
self.result = cb.result
|
||||||
|
self.date_finished = timezone.now()
|
||||||
|
except Exception as e:
|
||||||
|
self.status = 'failed'
|
||||||
|
self.summary = {'error': str(e)}
|
||||||
|
finally:
|
||||||
|
self.save()
|
||||||
|
self.update_task()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_finished(self):
|
||||||
|
return self.status in ['succeeded', 'failed']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_success(self):
|
||||||
|
return self.status == 'succeeded'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def time_cost(self):
|
||||||
|
if self.date_finished and self.date_start:
|
||||||
|
return (self.date_finished - self.date_start).total_seconds()
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_id(self):
|
||||||
|
return str(self.id).split('-')[-1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timedelta(self):
|
||||||
|
if self.date_start and self.date_finished:
|
||||||
|
return self.date_finished - self.date_start
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
|
@ -1,160 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
import uuid
|
|
||||||
import json
|
|
||||||
|
|
||||||
from celery.exceptions import SoftTimeLimitExceeded
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.translation import ugettext
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from terminal.notifications import CommandExecutionAlert
|
|
||||||
from assets.models import Asset
|
|
||||||
from common.utils import lazyproperty
|
|
||||||
from orgs.models import Organization
|
|
||||||
from orgs.mixins.models import OrgModelMixin
|
|
||||||
from orgs.utils import tmp_to_org
|
|
||||||
from ..ansible.runner import CommandRunner
|
|
||||||
from ..inventory import JMSInventory
|
|
||||||
|
|
||||||
|
|
||||||
class CommandExecution(OrgModelMixin):
|
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
|
||||||
hosts = models.ManyToManyField('assets.Asset')
|
|
||||||
account = models.CharField(max_length=128, default='', verbose_name=_('account'))
|
|
||||||
command = models.TextField(verbose_name=_("Command"))
|
|
||||||
_result = models.TextField(blank=True, null=True, verbose_name=_('Result'))
|
|
||||||
user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True)
|
|
||||||
is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
|
|
||||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
|
||||||
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'))
|
|
||||||
date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished'))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.command[:10]
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
with tmp_to_org(self.run_as.org_id):
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inventory(self):
|
|
||||||
if self.run_as.username_same_with_user:
|
|
||||||
username = self.user.username
|
|
||||||
else:
|
|
||||||
username = self.run_as.username
|
|
||||||
inv = JMSInventory(self.allow_assets, run_as=username, system_user=self.run_as)
|
|
||||||
return inv
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def user_display(self):
|
|
||||||
return str(self.user)
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def hosts_display(self):
|
|
||||||
return ','.join(self.hosts.all().values_list('name', flat=True))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def result(self):
|
|
||||||
if self._result:
|
|
||||||
return json.loads(self._result)
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
@result.setter
|
|
||||||
def result(self, item):
|
|
||||||
self._result = json.dumps(item)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_success(self):
|
|
||||||
if 'error' in self.result:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_hosts_names(self):
|
|
||||||
return ','.join(self.hosts.all().values_list('name', flat=True))
|
|
||||||
|
|
||||||
def cmd_filter_rules(self, asset_id=None):
|
|
||||||
from assets.models import CommandFilterRule
|
|
||||||
user_id = self.user.id
|
|
||||||
system_user_id = self.run_as.id
|
|
||||||
rules = CommandFilterRule.get_queryset(
|
|
||||||
user_id=user_id,
|
|
||||||
system_user_id=system_user_id,
|
|
||||||
asset_id=asset_id,
|
|
||||||
)
|
|
||||||
return rules
|
|
||||||
|
|
||||||
def is_command_can_run(self, command, asset_id=None):
|
|
||||||
for rule in self.cmd_filter_rules(asset_id=asset_id):
|
|
||||||
action, matched_cmd = rule.match(command)
|
|
||||||
if action == rule.ActionChoices.allow:
|
|
||||||
return True, None
|
|
||||||
elif action == rule.ActionChoices.deny:
|
|
||||||
return False, matched_cmd
|
|
||||||
return True, None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def allow_assets(self):
|
|
||||||
allow_asset_ids = []
|
|
||||||
for asset in self.hosts.all():
|
|
||||||
ok, __ = self.is_command_can_run(self.command, asset_id=asset.id)
|
|
||||||
if ok:
|
|
||||||
allow_asset_ids.append(asset.id)
|
|
||||||
allow_assets = Asset.objects.filter(id__in=allow_asset_ids)
|
|
||||||
return allow_assets
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
print('-' * 10 + ' ' + ugettext('Task start') + ' ' + '-' * 10)
|
|
||||||
org = Organization.get_instance(self.run_as.org_id)
|
|
||||||
org.change_to()
|
|
||||||
self.date_start = timezone.now()
|
|
||||||
ok, msg = self.is_command_can_run(self.command)
|
|
||||||
if ok:
|
|
||||||
allow_assets = self.allow_assets
|
|
||||||
deny_assets = set(list(self.hosts.all())) - set(list(allow_assets))
|
|
||||||
for asset in deny_assets:
|
|
||||||
print(f'资产{asset}: 命令{self.command}不允许执行')
|
|
||||||
if not allow_assets:
|
|
||||||
self.result = {
|
|
||||||
"error": 'There are currently no assets that can be executed'
|
|
||||||
}
|
|
||||||
self.save()
|
|
||||||
return self.result
|
|
||||||
runner = CommandRunner(self.inventory)
|
|
||||||
try:
|
|
||||||
host = allow_assets.first()
|
|
||||||
if host and host.is_windows():
|
|
||||||
shell = 'win_shell'
|
|
||||||
elif host and host.is_unixlike():
|
|
||||||
shell = 'shell'
|
|
||||||
else:
|
|
||||||
shell = 'raw'
|
|
||||||
result = runner.execute(self.command, 'all', module=shell)
|
|
||||||
self.result = result.results_command
|
|
||||||
except SoftTimeLimitExceeded as e:
|
|
||||||
print("Run timeout than 60s")
|
|
||||||
self.result = {"error": str(e)}
|
|
||||||
except Exception as e:
|
|
||||||
print("Error occur: {}".format(e))
|
|
||||||
self.result = {"error": str(e)}
|
|
||||||
else:
|
|
||||||
msg = _("Command `{}` is forbidden ........").format(self.command)
|
|
||||||
print('\033[31m' + msg + '\033[0m')
|
|
||||||
CommandExecutionAlert({
|
|
||||||
'input': self.command,
|
|
||||||
'assets': self.hosts.all(),
|
|
||||||
'user': str(self.user),
|
|
||||||
'risk_level': 5,
|
|
||||||
}).publish_async()
|
|
||||||
self.result = {"error": msg}
|
|
||||||
self.org_id = self.run_as.org_id
|
|
||||||
self.is_finished = True
|
|
||||||
self.date_finished = timezone.now()
|
|
||||||
self.save()
|
|
||||||
print('-' * 10 + ' ' + ugettext('Task end') + ' ' + '-' * 10)
|
|
||||||
return self.result
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Command execution")
|
|
|
@ -2,15 +2,34 @@ from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins.models import JMSOrgBaseModel
|
from orgs.mixins.models import JMSOrgBaseModel
|
||||||
from ..mixin import PeriodTaskModelMixin
|
from .base import BaseAnsibleExecution, BaseAnsibleTask
|
||||||
|
|
||||||
|
|
||||||
class PlaybookTask(PeriodTaskModelMixin, JMSOrgBaseModel):
|
class PlaybookTemplate(JMSOrgBaseModel):
|
||||||
assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets"))
|
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||||
account = models.CharField(max_length=128, default='root', verbose_name=_('Account'))
|
path = models.FilePathField(verbose_name=_("Path"))
|
||||||
playbook = models.FilePathField(max_length=1024, verbose_name=_("Playbook"))
|
comment = models.TextField(verbose_name=_("Comment"), blank=True)
|
||||||
owner = models.CharField(max_length=1024, verbose_name=_("Owner"))
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['name']
|
||||||
|
verbose_name = _("Playbook template")
|
||||||
|
unique_together = [('org_id', 'name')]
|
||||||
|
|
||||||
|
|
||||||
|
class Playbook(BaseAnsibleTask):
|
||||||
|
path = models.FilePathField(max_length=1024, verbose_name=_("Playbook"))
|
||||||
|
owner = models.ForeignKey('users.User', verbose_name=_("Owner"), on_delete=models.SET_NULL, null=True)
|
||||||
comment = models.TextField(blank=True, verbose_name=_("Comment"))
|
comment = models.TextField(blank=True, verbose_name=_("Comment"))
|
||||||
|
template = models.ForeignKey('PlaybookTemplate', verbose_name=_("Template"), on_delete=models.SET_NULL, null=True)
|
||||||
|
last_execution = models.ForeignKey('PlaybookExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
def get_register_task(self):
|
def get_register_task(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybookExecution(BaseAnsibleExecution):
|
||||||
|
task = models.ForeignKey('Playbook', verbose_name=_("Task"), on_delete=models.CASCADE)
|
||||||
|
path = models.FilePathField(max_length=1024, verbose_name=_("Run dir"))
|
||||||
|
|
|
@ -3,8 +3,7 @@ from __future__ import unicode_literals
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.shortcuts import reverse
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from ..models import AdHoc, AdHocExecution
|
||||||
from ..models import Task, AdHoc, AdHocExecution, CommandExecution
|
|
||||||
|
|
||||||
|
|
||||||
class AdHocExecutionSerializer(serializers.ModelSerializer):
|
class AdHocExecutionSerializer(serializers.ModelSerializer):
|
||||||
|
@ -50,36 +49,6 @@ class AdHocExecutionExcludeResultSerializer(AdHocExecutionSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TaskSerializer(BulkOrgResourceModelSerializer):
|
|
||||||
summary = serializers.ReadOnlyField(source='history_summary')
|
|
||||||
latest_execution = AdHocExecutionExcludeResultSerializer(read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Task
|
|
||||||
fields_mini = ['id', 'name', 'display_name']
|
|
||||||
fields_small = fields_mini + [
|
|
||||||
'interval', 'crontab',
|
|
||||||
'is_periodic', 'is_deleted',
|
|
||||||
'date_created', 'date_updated',
|
|
||||||
'comment',
|
|
||||||
]
|
|
||||||
fields_fk = ['latest_execution']
|
|
||||||
fields_custom = ['summary']
|
|
||||||
fields = fields_small + fields_fk + fields_custom
|
|
||||||
read_only_fields = [
|
|
||||||
'is_deleted', 'date_created', 'date_updated',
|
|
||||||
'latest_adhoc', 'latest_execution', 'total_run_amount',
|
|
||||||
'success_run_amount', 'summary',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class TaskDetailSerializer(TaskSerializer):
|
|
||||||
contents = serializers.ListField(source='latest_adhoc.tasks')
|
|
||||||
|
|
||||||
class Meta(TaskSerializer.Meta):
|
|
||||||
fields = TaskSerializer.Meta.fields + ['contents']
|
|
||||||
|
|
||||||
|
|
||||||
class AdHocSerializer(serializers.ModelSerializer):
|
class AdHocSerializer(serializers.ModelSerializer):
|
||||||
become_display = serializers.ReadOnlyField()
|
become_display = serializers.ReadOnlyField()
|
||||||
tasks = serializers.ListField()
|
tasks = serializers.ListField()
|
||||||
|
@ -127,26 +96,26 @@ class AdHocDetailSerializer(AdHocSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CommandExecutionSerializer(serializers.ModelSerializer):
|
# class CommandExecutionSerializer(serializers.ModelSerializer):
|
||||||
result = serializers.JSONField(read_only=True)
|
# result = serializers.JSONField(read_only=True)
|
||||||
log_url = serializers.SerializerMethodField()
|
# log_url = serializers.SerializerMethodField()
|
||||||
|
#
|
||||||
class Meta:
|
# class Meta:
|
||||||
model = CommandExecution
|
# model = CommandExecution
|
||||||
fields_mini = ['id']
|
# fields_mini = ['id']
|
||||||
fields_small = fields_mini + [
|
# fields_small = fields_mini + [
|
||||||
'command', 'result', 'log_url',
|
# 'command', 'result', 'log_url',
|
||||||
'is_finished', 'date_created', 'date_finished'
|
# 'is_finished', 'date_created', 'date_finished'
|
||||||
]
|
# ]
|
||||||
fields_m2m = ['hosts']
|
# fields_m2m = ['hosts']
|
||||||
fields = fields_small + fields_m2m
|
# fields = fields_small + fields_m2m
|
||||||
read_only_fields = [
|
# read_only_fields = [
|
||||||
'result', 'is_finished', 'log_url', 'date_created',
|
# 'result', 'is_finished', 'log_url', 'date_created',
|
||||||
'date_finished'
|
# 'date_finished'
|
||||||
]
|
# ]
|
||||||
ref_name = 'OpsCommandExecution'
|
# ref_name = 'OpsCommandExecution'
|
||||||
|
#
|
||||||
@staticmethod
|
# @staticmethod
|
||||||
def get_log_url(obj):
|
# def get_log_url(obj):
|
||||||
return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id})
|
# return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id})
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ from .celery.utils import (
|
||||||
create_or_update_celery_periodic_tasks, get_celery_periodic_task,
|
create_or_update_celery_periodic_tasks, get_celery_periodic_task,
|
||||||
disable_celery_periodic_task, delete_celery_periodic_task
|
disable_celery_periodic_task, delete_celery_periodic_task
|
||||||
)
|
)
|
||||||
from .models import Task, CommandExecution, CeleryTask
|
from .models import CommandExecution, CeleryTask
|
||||||
from .notifications import ServerPerformanceCheckUtil
|
from .notifications import ServerPerformanceCheckUtil
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
|
@ -12,14 +12,11 @@ app_name = "ops"
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
bulk_router = BulkRouter()
|
bulk_router = BulkRouter()
|
||||||
|
|
||||||
bulk_router.register(r'tasks', api.TaskViewSet, 'task')
|
|
||||||
router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
|
router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
|
||||||
router.register(r'adhoc-executions', api.AdHocRunHistoryViewSet, 'execution')
|
router.register(r'adhoc-executions', api.AdHocExecutionViewSet, 'execution')
|
||||||
router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution')
|
|
||||||
router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task')
|
router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('tasks/<uuid:pk>/run/', api.TaskRun.as_view(), name='task-run'),
|
|
||||||
path('celery/task/<uuid:pk>/log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
|
path('celery/task/<uuid:pk>/log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
|
||||||
path('celery/task/<uuid:pk>/result/', api.CeleryResultApi.as_view(), name='celery-result'),
|
path('celery/task/<uuid:pk>/result/', api.CeleryResultApi.as_view(), name='celery-result'),
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue