diff --git a/apps/assets/models/automation/base.py b/apps/assets/models/automation/base.py
index 27c971e0d..a9b8ab087 100644
--- a/apps/assets/models/automation/base.py
+++ b/apps/assets/models/automation/base.py
@@ -4,15 +4,14 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.const.choices import Trigger
-from common.mixins.models import CommonModelMixin
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.tasks import execute_automation_strategy
from ops.task_handlers import ExecutionManager
-class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
+class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin):
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
nodes = models.ManyToManyField(
'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')
)
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')
)
trigger = models.CharField(
diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py
index f7820ccb1..937d0d95c 100644
--- a/apps/assets/models/label.py
+++ b/apps/assets/models/label.py
@@ -14,16 +14,12 @@ class Label(OrgModelMixin):
("S", _("System")),
("U", _("User"))
)
- id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_("Name"))
value = models.CharField(max_length=128, verbose_name=_("Value"))
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
default=USER_CATEGORY, verbose_name=_("Category"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
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
def get_queryset_group_by_name(cls):
diff --git a/apps/audits/api.py b/apps/audits/api.py
index 6cb2e1283..a61694e85 100644
--- a/apps/audits/api.py
+++ b/apps/audits/api.py
@@ -11,16 +11,14 @@ from common.drf.filters import DatetimeRangeFilter
from common.api import CommonGenericViewSet
from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin
from orgs.utils import current_org
-from ops.models import CommandExecution
+# from ops.models import CommandExecution
from . import filters
from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog
-from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecutionSerializer
-from .serializers import OperateLogSerializer, PasswordChangeLogSerializer, CommandExecutionHostsRelationSerializer
+from .serializers import FTPLogSerializer, UserLoginLogSerializer
+from .serializers import OperateLogSerializer, PasswordChangeLogSerializer
-class FTPLogViewSet(CreateModelMixin,
- ListModelMixin,
- OrgGenericViewSet):
+class FTPLogViewSet(CreateModelMixin, ListModelMixin, OrgGenericViewSet):
model = FTPLog
serializer_class = FTPLogSerializer
extra_filter_backends = [DatetimeRangeFilter]
@@ -98,53 +96,53 @@ class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet):
)
return queryset
-
-class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
- model = CommandExecution
- serializer_class = CommandExecutionSerializer
- extra_filter_backends = [DatetimeRangeFilter]
- date_range_filter_fields = [
- ('date_start', ('date_from', 'date_to'))
- ]
- filterset_fields = [
- 'user__name', 'user__username', 'command',
- 'account', 'is_finished'
- ]
- search_fields = [
- 'command', 'user__name', 'user__username',
- 'account__username',
- ]
- ordering = ['-date_created']
-
- def get_queryset(self):
- queryset = super().get_queryset()
- if getattr(self, 'swagger_fake_view', False):
- return queryset.model.objects.none()
- if current_org.is_root():
- return queryset
- # queryset = queryset.filter(run_as__org_id=current_org.org_id())
- return queryset
-
-
-class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
- serializer_class = CommandExecutionHostsRelationSerializer
- m2m_field = CommandExecution.hosts.field
- filterset_fields = [
- 'id', 'asset', 'commandexecution'
- ]
- search_fields = ('asset__name', )
- http_method_names = ['options', 'get']
- rbac_perms = {
- 'GET': 'ops.view_commandexecution',
- 'list': 'ops.view_commandexecution',
- }
-
- def get_queryset(self):
- queryset = super().get_queryset()
- queryset = queryset.annotate(
- asset_display=Concat(
- F('asset__name'), Value('('),
- F('asset__address'), Value(')')
- )
- )
- return queryset
+# Todo: 看看怎么搞
+# class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
+# model = CommandExecution
+# serializer_class = CommandExecutionSerializer
+# extra_filter_backends = [DatetimeRangeFilter]
+# date_range_filter_fields = [
+# ('date_start', ('date_from', 'date_to'))
+# ]
+# filterset_fields = [
+# 'user__name', 'user__username', 'command',
+# 'account', 'is_finished'
+# ]
+# search_fields = [
+# 'command', 'user__name', 'user__username',
+# 'account__username',
+# ]
+# ordering = ['-date_created']
+#
+# def get_queryset(self):
+# queryset = super().get_queryset()
+# if getattr(self, 'swagger_fake_view', False):
+# return queryset.model.objects.none()
+# if current_org.is_root():
+# return queryset
+# # queryset = queryset.filter(run_as__org_id=current_org.org_id())
+# return queryset
+#
+#
+# class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
+# serializer_class = CommandExecutionHostsRelationSerializer
+# m2m_field = CommandExecution.hosts.field
+# filterset_fields = [
+# 'id', 'asset', 'commandexecution'
+# ]
+# search_fields = ('asset__name', )
+# http_method_names = ['options', 'get']
+# rbac_perms = {
+# 'GET': 'ops.view_commandexecution',
+# 'list': 'ops.view_commandexecution',
+# }
+#
+# def get_queryset(self):
+# queryset = super().get_queryset()
+# queryset = queryset.annotate(
+# asset_display=Concat(
+# F('asset__name'), Value('('),
+# F('asset__address'), Value(')')
+# )
+# )
+# return queryset
diff --git a/apps/audits/filters.py b/apps/audits/filters.py
index a6c44b5c5..c15c22b56 100644
--- a/apps/audits/filters.py
+++ b/apps/audits/filters.py
@@ -5,10 +5,9 @@ from rest_framework import filters
from rest_framework.compat import coreapi, coreschema
from orgs.utils import current_org
-from ops.models import CommandExecution
from common.drf.filters import BaseFilterSet
-__all__ = ['CurrentOrgMembersFilter', 'CommandExecutionFilter']
+__all__ = ['CurrentOrgMembersFilter']
class CurrentOrgMembersFilter(filters.BaseFilterBackend):
@@ -35,21 +34,21 @@ class CurrentOrgMembersFilter(filters.BaseFilterBackend):
queryset = queryset.filter(user__in=self._get_user_list())
return queryset
-
-class CommandExecutionFilter(BaseFilterSet):
- hostname_ip = CharFilter(method='filter_hostname_ip')
-
- class Meta:
- model = CommandExecution.hosts.through
- fields = (
- 'id', 'asset', 'commandexecution', 'hostname_ip'
- )
-
- def filter_hostname_ip(self, queryset, name, value):
- queryset = queryset.annotate(
- hostname_ip=Concat(
- F('asset__hostname'), Value('('),
- F('asset__address'), Value(')')
- )
- ).filter(hostname_ip__icontains=value)
- return queryset
+#
+# class CommandExecutionFilter(BaseFilterSet):
+# hostname_ip = CharFilter(method='filter_hostname_ip')
+#
+# class Meta:
+# model = CommandExecution.hosts.through
+# fields = (
+# 'id', 'asset', 'commandexecution', 'hostname_ip'
+# )
+#
+# def filter_hostname_ip(self, queryset, name, value):
+# queryset = queryset.annotate(
+# hostname_ip=Concat(
+# F('asset__hostname'), Value('('),
+# F('asset__address'), Value(')')
+# )
+# ).filter(hostname_ip__icontains=value)
+# return queryset
diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py
index 8b9d28005..0f595be25 100644
--- a/apps/audits/serializers.py
+++ b/apps/audits/serializers.py
@@ -5,7 +5,6 @@ from rest_framework import serializers
from common.drf.serializers import BulkSerializerMixin
from terminal.models import Session
-from ops.models import CommandExecution
from . import models
@@ -76,42 +75,42 @@ class SessionAuditSerializer(serializers.ModelSerializer):
model = Session
fields = '__all__'
-
-class CommandExecutionSerializer(serializers.ModelSerializer):
- is_success = serializers.BooleanField(read_only=True, label=_('Is success'))
- hosts_display = serializers.ListSerializer(
- child=serializers.CharField(), source='hosts', read_only=True, label=_('Hosts display')
- )
-
- class Meta:
- model = CommandExecution
- fields_mini = ['id']
- fields_small = fields_mini + [
- 'command', 'is_finished', 'user',
- 'date_start', 'result', 'is_success', 'org_id'
- ]
- fields = fields_small + ['hosts', 'hosts_display', 'user_display']
- extra_kwargs = {
- 'result': {'label': _('Result')}, # model 上的方法,只能在这修改
- 'is_success': {'label': _('Is success')},
- 'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改
- 'user': {'label': _('User')},
- 'user_display': {'label': _('User display')},
- }
-
- @classmethod
- def setup_eager_loading(cls, queryset):
- """ Perform necessary eager loading of data. """
- queryset = queryset.prefetch_related('user', 'hosts')
- return queryset
-
-
-class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.ModelSerializer):
- asset_display = serializers.ReadOnlyField()
- commandexecution_display = serializers.ReadOnlyField()
-
- class Meta:
- model = CommandExecution.hosts.through
- fields = [
- 'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display'
- ]
+#
+# class CommandExecutionSerializer(serializers.ModelSerializer):
+# is_success = serializers.BooleanField(read_only=True, label=_('Is success'))
+# hosts_display = serializers.ListSerializer(
+# child=serializers.CharField(), source='hosts', read_only=True, label=_('Hosts display')
+# )
+#
+# class Meta:
+# model = CommandExecution
+# fields_mini = ['id']
+# fields_small = fields_mini + [
+# 'command', 'is_finished', 'user',
+# 'date_start', 'result', 'is_success', 'org_id'
+# ]
+# fields = fields_small + ['hosts', 'hosts_display', 'user_display']
+# extra_kwargs = {
+# 'result': {'label': _('Result')}, # model 上的方法,只能在这修改
+# 'is_success': {'label': _('Is success')},
+# 'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改
+# 'user': {'label': _('User')},
+# 'user_display': {'label': _('User display')},
+# }
+#
+# @classmethod
+# def setup_eager_loading(cls, queryset):
+# """ Perform necessary eager loading of data. """
+# queryset = queryset.prefetch_related('user', 'hosts')
+# return queryset
+#
+#
+# class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.ModelSerializer):
+# asset_display = serializers.ReadOnlyField()
+# commandexecution_display = serializers.ReadOnlyField()
+#
+# class Meta:
+# model = CommandExecution.hosts.through
+# fields = [
+# 'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display'
+# ]
diff --git a/apps/audits/urls/api_urls.py b/apps/audits/urls/api_urls.py
index 7301b67fb..902c65fbf 100644
--- a/apps/audits/urls/api_urls.py
+++ b/apps/audits/urls/api_urls.py
@@ -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'operate-logs', api.OperateLogViewSet, 'operate-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-executions-hosts-relations', api.CommandExecutionHostRelationViewSet, 'command-executions-hosts-relation')
+# router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log')
+# router.register(r'command-executions-hosts-relations', api.CommandExecutionHostRelationViewSet, 'command-executions-hosts-relation')
urlpatterns = [
diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py
index d009238cd..8456d9fa0 100644
--- a/apps/jumpserver/settings/base.py
+++ b/apps/jumpserver/settings/base.py
@@ -16,6 +16,7 @@ VERSION = const.VERSION
BASE_DIR = const.BASE_DIR
PROJECT_DIR = const.PROJECT_DIR
DATA_DIR = os.path.join(PROJECT_DIR, 'data')
+ANSIBLE_DIR = os.path.join(DATA_DIR, 'ansible')
CERTS_DIR = os.path.join(DATA_DIR, 'certs')
# Quick-start development settings - unsuitable for production
diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py
index 8b6ad1f8f..59734b07d 100644
--- a/apps/ops/ansible/callback.py
+++ b/apps/ops/ansible/callback.py
@@ -15,7 +15,7 @@ class DefaultCallback:
dark={},
skipped=[],
)
- self.status = 'starting'
+ self.status = 'running'
self.finished = False
def is_success(self):
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index 2382525ed..4da027696 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -3,21 +3,19 @@ from collections import defaultdict
import json
-__all__ = [
- 'JMSInventory',
-]
+__all__ = ['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 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 host_var_callback:
"""
self.assets = self.clean_assets(assets)
- self.account_username = account_username
+ self.account_username = account
self.account_policy = account_policy
self.host_var_callback = host_var_callback
diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py
index 6c339eba6..a420fb8a4 100644
--- a/apps/ops/ansible/runner.py
+++ b/apps/ops/ansible/runner.py
@@ -68,3 +68,11 @@ class PlaybookRunner:
**kwargs
)
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)
diff --git a/apps/ops/api/__init__.py b/apps/ops/api/__init__.py
index e59889cd2..8eb5356e4 100644
--- a/apps/ops/api/__init__.py
+++ b/apps/ops/api/__init__.py
@@ -2,4 +2,3 @@
#
from .adhoc import *
from .celery import *
-from .command import *
diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py
index 0cc7b6d55..8644ac5d2 100644
--- a/apps/ops/api/adhoc.py
+++ b/apps/ops/api/adhoc.py
@@ -6,52 +6,18 @@ from rest_framework import viewsets, generics
from rest_framework.views import Response
from common.drf.serializers import CeleryTaskSerializer
-from ..models import Task, AdHoc, AdHocExecution
+from ..models import AdHoc, AdHocExecution
from ..serializers import (
- TaskSerializer,
AdHocSerializer,
AdHocExecutionSerializer,
- TaskDetailSerializer,
AdHocDetailSerializer,
)
-from ..tasks import run_ansible_task
-from orgs.mixins.api import OrgBulkModelViewSet
__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):
queryset = AdHoc.objects.all()
serializer_class = AdHocSerializer
@@ -61,23 +27,17 @@ class AdHocViewSet(viewsets.ModelViewSet):
return AdHocDetailSerializer
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 AdHocRunHistoryViewSet(viewsets.ModelViewSet):
+class AdHocExecutionViewSet(viewsets.ModelViewSet):
queryset = AdHocExecution.objects.all()
serializer_class = AdHocExecutionSerializer
def get_queryset(self):
task_id = self.request.query_params.get('task')
adhoc_id = self.request.query_params.get('adhoc')
+
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()
self.queryset = self.queryset.filter(adhoc__in=adhocs)
diff --git a/apps/ops/api/command.py b/apps/ops/api/command.py
deleted file mode 100644
index 1cf7950a6..000000000
--- a/apps/ops/api/command.py
+++ /dev/null
@@ -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)
- ))
diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py
deleted file mode 100644
index d6943f5c5..000000000
--- a/apps/ops/inventory.py
+++ /dev/null
@@ -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
- }
diff --git a/apps/ops/migrations/0024_auto_20221008_1514.py b/apps/ops/migrations/0024_auto_20221008_1514.py
new file mode 100644
index 000000000..e208af96e
--- /dev/null
+++ b/apps/ops/migrations/0024_auto_20221008_1514.py
@@ -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',
+ ),
+ ]
diff --git a/apps/ops/migrations/0025_auto_20221008_1631.py b/apps/ops/migrations/0025_auto_20221008_1631.py
new file mode 100644
index 000000000..7e814c3d1
--- /dev/null
+++ b/apps/ops/migrations/0025_auto_20221008_1631.py
@@ -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'),
+ ),
+ ]
diff --git a/apps/ops/mixin.py b/apps/ops/mixin.py
index e64a763fc..4d2fd52a7 100644
--- a/apps/ops/mixin.py
+++ b/apps/ops/mixin.py
@@ -14,12 +14,10 @@ from .celery.utils import (
__all__ = [
'PeriodTaskModelMixin', 'PeriodTaskSerializerMixin',
- 'PeriodTaskFormMixin',
]
class PeriodTaskModelMixin(models.Model):
- id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(
max_length=128, unique=False, verbose_name=_("Name")
)
@@ -140,42 +138,3 @@ class PeriodTaskSerializerMixin(serializers.Serializer):
msg = _("Require periodic or regularly perform setting")
raise serializers.ValidationError(msg)
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>
"
- "Tips: "
- "Using 5 digits linux crontab expressions "
- " "
- "(Online tools)
"
- "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
-
-
diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py
index 0a9ed463c..fcd8bd8f7 100644
--- a/apps/ops/models/__init__.py
+++ b/apps/ops/models/__init__.py
@@ -3,4 +3,3 @@
from .adhoc import *
from .celery import *
-from .command import *
diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py
index 1d2920206..565df9f3e 100644
--- a/apps/ops/models/adhoc.py
+++ b/apps/ops/models/adhoc.py
@@ -1,337 +1,41 @@
# ~*~ coding: utf-8 ~*~
-import uuid
-import os
-import time
-import datetime
-from celery import current_task
from django.db import models
-from django.conf import settings
-from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
-from common.utils import get_logger, lazyproperty
-from common.utils.translate import translate_value
-from common.db.fields import (
- JsonListTextField, JsonDictCharField, EncryptJsonDictCharField,
- JsonDictTextField,
-)
-from orgs.mixins.models import OrgModelMixin
-from ..ansible import AdHocRunner, AnsibleError
-from ..inventory import JMSInventory
-from ..mixin import PeriodTaskModelMixin
+from common.utils import get_logger
+from .base import BaseAnsibleTask, BaseAnsibleExecution
+from ..ansible import AdHocRunner
-__all__ = ["Task", "AdHoc", "AdHocExecution"]
+__all__ = ["AdHoc", "AdHocExecution"]
logger = get_logger(__file__)
-class Task(PeriodTaskModelMixin, OrgModelMixin):
- """
- This task is different ansible task, Task like 'push system user', 'get asset info' ..
- One task can have some versions of adhoc, run a task only run the latest version adhoc
- """
- 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
+class AdHoc(BaseAnsibleTask):
+ pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all')
+ module = models.CharField(max_length=128, default='shell', verbose_name=_('Module'))
+ 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)
def __str__(self):
- return self.name + '@' + str(self.org_id)
-
- 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'))
- ]
+ return "{}: {}".format(self.module, self.args)
-class AdHoc(OrgModelMixin):
- """
- 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 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):
+class AdHocExecution(BaseAnsibleExecution):
"""
AdHoc running history.
"""
- id = models.UUIDField(default=uuid.uuid4, primary_key=True)
- 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'))
+ task = models.ForeignKey('AdHoc', verbose_name=_("Adhoc"), related_name='executions', on_delete=models.CASCADE)
- @property
- def short_id(self):
- return str(self.id).split('-')[-1]
-
- @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
+ def get_runner(self):
+ return AdHocRunner(
+ self.task.inventory, self.task.module, self.task.args,
+ pattern=self.task.pattern, project_dir=self.private_dir
)
- @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:
db_table = "ops_adhoc_execution"
get_latest_by = 'date_start'
diff --git a/apps/ops/models/base.py b/apps/ops/models/base.py
new file mode 100644
index 000000000..2992173c3
--- /dev/null
+++ b/apps/ops/models/base.py
@@ -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
+
+
diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py
deleted file mode 100644
index cb6023564..000000000
--- a/apps/ops/models/command.py
+++ /dev/null
@@ -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")
diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py
index aaec7a4ef..aec59bfb0 100644
--- a/apps/ops/models/playbook.py
+++ b/apps/ops/models/playbook.py
@@ -2,15 +2,34 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from orgs.mixins.models import JMSOrgBaseModel
-from ..mixin import PeriodTaskModelMixin
+from .base import BaseAnsibleExecution, BaseAnsibleTask
-class PlaybookTask(PeriodTaskModelMixin, JMSOrgBaseModel):
- assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets"))
- account = models.CharField(max_length=128, default='root', verbose_name=_('Account'))
- playbook = models.FilePathField(max_length=1024, verbose_name=_("Playbook"))
- owner = models.CharField(max_length=1024, verbose_name=_("Owner"))
+class PlaybookTemplate(JMSOrgBaseModel):
+ name = models.CharField(max_length=128, verbose_name=_("Name"))
+ path = models.FilePathField(verbose_name=_("Path"))
+ comment = models.TextField(verbose_name=_("Comment"), blank=True)
+
+ 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"))
+ 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):
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"))
diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py
index 50b1faeba..b6522b85f 100644
--- a/apps/ops/serializers/adhoc.py
+++ b/apps/ops/serializers/adhoc.py
@@ -3,8 +3,7 @@ from __future__ import unicode_literals
from rest_framework import serializers
from django.shortcuts import reverse
-from orgs.mixins.serializers import BulkOrgResourceModelSerializer
-from ..models import Task, AdHoc, AdHocExecution, CommandExecution
+from ..models import AdHoc, AdHocExecution
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):
become_display = serializers.ReadOnlyField()
tasks = serializers.ListField()
@@ -127,26 +96,26 @@ class AdHocDetailSerializer(AdHocSerializer):
]
-class CommandExecutionSerializer(serializers.ModelSerializer):
- result = serializers.JSONField(read_only=True)
- log_url = serializers.SerializerMethodField()
-
- class Meta:
- model = CommandExecution
- fields_mini = ['id']
- fields_small = fields_mini + [
- 'command', 'result', 'log_url',
- 'is_finished', 'date_created', 'date_finished'
- ]
- fields_m2m = ['hosts']
- fields = fields_small + fields_m2m
- read_only_fields = [
- 'result', 'is_finished', 'log_url', 'date_created',
- 'date_finished'
- ]
- ref_name = 'OpsCommandExecution'
-
- @staticmethod
- def get_log_url(obj):
- return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id})
+# class CommandExecutionSerializer(serializers.ModelSerializer):
+# result = serializers.JSONField(read_only=True)
+# log_url = serializers.SerializerMethodField()
+#
+# class Meta:
+# model = CommandExecution
+# fields_mini = ['id']
+# fields_small = fields_mini + [
+# 'command', 'result', 'log_url',
+# 'is_finished', 'date_created', 'date_finished'
+# ]
+# fields_m2m = ['hosts']
+# fields = fields_small + fields_m2m
+# read_only_fields = [
+# 'result', 'is_finished', 'log_url', 'date_created',
+# 'date_finished'
+# ]
+# ref_name = 'OpsCommandExecution'
+#
+# @staticmethod
+# def get_log_url(obj):
+# return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id})
diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py
index cb21b5c3d..0ef430d7a 100644
--- a/apps/ops/tasks.py
+++ b/apps/ops/tasks.py
@@ -20,7 +20,7 @@ from .celery.utils import (
create_or_update_celery_periodic_tasks, get_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
logger = get_logger(__file__)
diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py
index a5838073f..49038b9b1 100644
--- a/apps/ops/urls/api_urls.py
+++ b/apps/ops/urls/api_urls.py
@@ -12,14 +12,11 @@ app_name = "ops"
router = DefaultRouter()
bulk_router = BulkRouter()
-bulk_router.register(r'tasks', api.TaskViewSet, 'task')
router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
-router.register(r'adhoc-executions', api.AdHocRunHistoryViewSet, 'execution')
-router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution')
+router.register(r'adhoc-executions', api.AdHocExecutionViewSet, 'execution')
router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task')
urlpatterns = [
- path('tasks//run/', api.TaskRun.as_view(), name='task-run'),
path('celery/task//log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
path('celery/task//result/', api.CeleryResultApi.as_view(), name='celery-result'),