perf: 修改 ansible 表结构

pull/8970/head
ibuler 2022-10-08 16:55:14 +08:00 committed by 老广
parent df5e63b3be
commit 0fb4b52232
25 changed files with 438 additions and 983 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ class DefaultCallback:
dark={},
skipped=[],
)
self.status = 'starting'
self.status = 'running'
self.finished = False
def is_success(self):

View File

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

View File

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

View File

@ -2,4 +2,3 @@
#
from .adhoc import *
from .celery import *
from .command import *

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,4 +3,3 @@
from .adhoc import *
from .celery import *
from .command import *

View File

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

106
apps/ops/models/base.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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/<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>/result/', api.CeleryResultApi.as_view(), name='celery-result'),