Commit 0c9e24dc authored by ibuler's avatar ibuler

[Feture] 添加ops 页面

parent 18fd04d6
......@@ -26,12 +26,13 @@ from .hands import IsSuperUser, IsAppUser, IsValidUser, \
get_user_granted_assets, push_users
from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser
from . import serializers
from .tasks import update_assets_hardware_info
from .utils import test_admin_user_connective_manual
from .tasks import update_assets_hardware_info, test_admin_user_connectability_manual
class AssetViewSet(IDInFilterMixin, BulkModelViewSet):
"""API endpoint that allows Asset to be viewed or edited."""
"""
API endpoint that allows Asset to be viewed or edited.
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsValidUser,)
......@@ -195,7 +196,7 @@ class AssetAdminUserTestView(AssetRefreshHardwareView):
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
result = test_admin_user_connective_manual([asset])
result = test_admin_user_connectability_manual(asset)
if result:
return Response('1')
else:
......
# -*- coding: utf-8 -*-
#
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
print("Import assets model")
from .user import AdminUser, SystemUser
from .cluster import *
from .group import *
......
......@@ -7,6 +7,7 @@ import uuid
from django.db import models
import logging
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from .cluster import Cluster
from .group import AssetGroup
......@@ -21,6 +22,7 @@ def get_default_cluster():
class Asset(models.Model):
# Todo: Move them to settings
STATUS_CHOICES = (
('In use', _('In use')),
('Out of use', _('Out of use')),
......@@ -103,6 +105,9 @@ class Asset(models.Model):
'groups': [group.name for group in self.groups.all()],
}
def is_connective(self):
return cache.get(self.hostname)
def _to_secret_json(self):
"""
Ansible use it create inventory
......
......@@ -19,7 +19,6 @@ logger = logging.getLogger(__name__)
class AssetGroup(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
system_users = models.ManyToManyField(SystemUser, related_name='asset_groups', blank=True)
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
......
......@@ -201,21 +201,6 @@ class SystemUser(models.Model):
def public_key(self, public_key_raw):
self._public_key = signer.sign(public_key_raw)
def get_assets_inherit_from_asset_groups(self):
assets = set()
asset_groups = self.asset_groups.all()
for asset_group in asset_groups:
for asset in asset_group.assets.all():
setattr(asset, 'is_inherit_from_asset_groups', True)
setattr(asset, 'inherit_from_asset_groups',
getattr(asset, 'inherit_from_asset_groups', set()).add(asset_group))
assets.add(asset)
return assets
def get_assets(self):
assets = set(self.assets.all()) | self.get_assets_inherit_from_asset_groups()
return list(assets)
def _to_secret_json(self):
"""Push system user use it"""
return {
......@@ -232,10 +217,6 @@ class SystemUser(models.Model):
def assets_amount(self):
return self.assets.count()
@property
def asset_group_amount(self):
return self.asset_groups.count()
def to_json(self):
return {
'id': self.id,
......
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from rest_framework import viewsets, serializers, generics
from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser
from common.mixins import IDInFilterMixin
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser
from .tasks import SYSTEM_USER_CONN_CACHE_KEY_PREFIX, ADMIN_USER_CONN_CACHE_KEY_PREFIX
class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
assets_amount = serializers.SerializerMethodField()
......@@ -64,11 +64,20 @@ class ClusterUpdateAssetsSerializer(serializers.ModelSerializer):
class AdminUserSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
unreachable_amount = serializers.SerializerMethodField()
class Meta:
model = AdminUser
fields = '__all__'
@staticmethod
def get_unreachable_amount(obj):
data = cache.get(ADMIN_USER_CONN_CACHE_KEY_PREFIX + obj.name)
if data:
return len(data.get('dark'))
else:
return 'Unknown'
def get_field_names(self, declared_fields, info):
fields = super(AdminUserSerializer, self).get_field_names(declared_fields, info)
fields.append('assets_amount')
......@@ -76,10 +85,20 @@ class AdminUserSerializer(serializers.ModelSerializer):
class SystemUserSerializer(serializers.ModelSerializer):
unreachable_amount = serializers.SerializerMethodField()
class Meta:
model = SystemUser
exclude = ('_password', '_private_key', '_public_key')
@staticmethod
def get_unreachable_amount(obj):
data = cache.get(SYSTEM_USER_CONN_CACHE_KEY_PREFIX + obj.name)
if data:
return len(data.get('dark'))
else:
return "Unknown"
def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
fields.extend(['assets_amount'])
......@@ -167,8 +186,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
@staticmethod
def get_system_users_join(obj):
return ', '.join([system_user.username
for system_user in obj.system_users_granted])
return ', '.join([system_user.username for system_user in obj.system_users_granted])
class MyAssetGrantedSerializer(AssetGrantedSerializer):
......
# ~*~ coding: utf-8 ~*~
from celery import shared_task
import json
from celery import shared_task
from django.core.cache import cache
from ops.tasks import run_AdHoc
from common.utils import get_object_or_none, capacity_convert, sum_capacity
from assets.models import SystemUser, AdminUser
from common.utils import get_object_or_none, capacity_convert, sum_capacity, encrypt_password, get_logger
from .models import Asset
FORKS = 10
TIMEOUT = 60
logger = get_logger(__file__)
ADMIN_USER_CONN_CACHE_KEY_PREFIX = "ADMIN_USER_CONN_"
SYSTEM_USER_CONN_CACHE_KEY_PREFIX = 'SYSTEM_USER_CONN_'
@shared_task
def update_assets_hardware_info(assets):
task_tuple = (
('setup', ''),
)
summary, result = run_AdHoc(task_tuple, assets, record=False)
for hostname, info in result['contacted'].items():
"""
Using ansible api to update asset hardware info
:param assets: asset seq
:return: result summary ['contacted': {}, 'dark': {}]
"""
from ops.utils import run_adhoc
name = "GET_ASSETS_HARDWARE_INFO"
tasks = [
{
'name': name,
'action': {
'module': 'setup'
}
}
]
hostname_list = [asset.hostname for asset in assets]
result = run_adhoc(hostname_list, pattern='all', tasks=tasks,
name=name, run_as_admin=True)
summary, result_raw = result.results_summary, result.results_raw
for hostname, info in result_raw['ok'].items():
if info:
info = info[0]['ansible_facts']
info = info[name]['ansible_facts']
else:
continue
asset = get_object_or_none(Asset, hostname=hostname)
......@@ -58,23 +80,193 @@ def update_assets_hardware_info(assets):
@shared_task
def update_assets_hardware_period():
"""
Update asset hardware period task
:return:
"""
assets = Asset.objects.filter(type__in=['Server', 'VM'])
update_assets_hardware_info(assets)
@shared_task
def test_admin_user_connective_period():
assets = Asset.objects.filter(type__in=['Server', 'VM'])
task_tuple = (
('ping', ''),
)
summary, _ = run_AdHoc(task_tuple, assets, record=False)
for i in summary['success']:
cache.set(i, '1', 2*60*60*60)
for i, msg in summary['failed']:
cache.set(i, '0', 60*60*60)
return summary
def test_admin_user_connectability(admin_user):
"""
Test asset admin user can connect or not. Using ansible api do that
:param admin_user:
:return:
"""
from ops.utils import run_adhoc
assets = admin_user.assets.all()
# assets = Asset.objects.filter(type__in=['Server', 'VM'])
hosts = [asset.hostname for asset in assets]
tasks = [
{
"name": "TEST_ADMIN_CONNECTIVE",
"action": {
"module": "ping",
}
}
]
result = run_adhoc(hosts, tasks=tasks, pattern="all", run_as_admin=True)
return result.results_summary
@shared_task
def test_admin_user_connectability_period():
# assets = Asset.objects.filter(type__in=['Server', 'VM'])
admin_users = AdminUser.objects.all()
for admin_user in admin_users:
summary = test_admin_user_connectability(admin_user)
cache.set(ADMIN_USER_CONN_CACHE_KEY_PREFIX + admin_user.name, summary, 60*60*60)
for i in summary['contacted']:
cache.set(ADMIN_USER_CONN_CACHE_KEY_PREFIX + i, 1, 60*60*60)
for i in summary['dark']:
cache.set(ADMIN_USER_CONN_CACHE_KEY_PREFIX + i, 0, 60*60*60)
def test_admin_user_connectability_manual(asset):
from ops.utils import run_adhoc
# assets = Asset.objects.filter(type__in=['Server', 'VM'])
hosts = [asset.hostname]
tasks = [
{
"name": "TEST_ADMIN_CONNECTIVE",
"action": {
"module": "ping",
}
}
]
result = run_adhoc(hosts, tasks=tasks, pattern="all", run_as_admin=True)
if result.results_summary['dark']:
return False
else:
return True
@shared_task
def test_system_user_connectability(system_user):
"""
Test system cant connect his assets or not.
:param system_user:
:return:
"""
from ops.utils import run_adhoc
assets = system_user.assets.all()
hosts = [asset.hostname for asset in assets]
tasks = [
{
"name": "TEST_SYSTEM_USER_CONNECTIVE",
"action": {
"module": "ping",
}
}
]
result = run_adhoc(hosts, tasks=tasks, pattern="all", run_as=system_user.name)
return result.results_summary
@shared_task
def test_system_user_connectability_period():
for system_user in SystemUser.objects.all():
summary = test_system_user_connectability(system_user)
cache.set(SYSTEM_USER_CONN_CACHE_KEY_PREFIX + system_user.name , summary, 60*60*60)
def get_push_system_user_tasks(system_user):
tasks = [
{
'name': 'Add user',
'action': {
'module': 'user',
'args': 'name={} shell={} state=present password={}'.format(
system_user.username, system_user.shell,
encrypt_password(system_user.password),
),
}
},
{
'name': 'Set authorized key',
'action': {
'module': 'authorized_key',
'args': "user={} state=present key='{}'".format(
system_user.username, system_user.public_key
)
}
},
{
'name': 'Set sudoers',
'action': {
'module': 'lineinfile',
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format(
system_user.username,
system_user.sudo,
)
}
}
]
return tasks
PUSH_SYSTEM_USER_PERIOD_TASK_NAME = 'PUSH SYSTEM USER {} PERIOD...'
PUSH_SYSTEM_USER_TASK_NAME = 'PUSH SYSTEM USER {} ASSETS'
def get_push_system_user_task(system_user):
from ops.utils import get_task_by_name
task = get_task_by_name(PUSH_SYSTEM_USER_PERIOD_TASK_NAME.format(system_user.name))
return task
def push_system_user(system_user, assets, name):
from ops.utils import get_task_by_name, run_adhoc_object, \
create_task, create_adhoc
if system_user.auto_push and assets:
task = get_task_by_name(name)
if not task:
task = create_task(name, created_by="System")
task.save()
tasks = get_push_system_user_tasks(system_user)
hosts = [asset.hostname for asset in assets]
options = {'forks': FORKS, 'timeout': TIMEOUT}
adhoc = task.get_latest_adhoc()
if not adhoc or adhoc.task != tasks or adhoc.hosts != hosts:
adhoc = create_adhoc(task=task, tasks=tasks, pattern='all',
options=options, hosts=hosts, run_as_admin=True)
return run_adhoc_object(adhoc)
@shared_task
def push_system_user_period():
logger.debug("Push system user period")
for s in SystemUser.objects.filter(auto_push=True):
assets = s.assets.all()
name = PUSH_SYSTEM_USER_PERIOD_TASK_NAME.format(s.name)
push_system_user(s, assets, name)
def push_system_user_to_assets_if_need(system_user, assets=None, asset_groups=None):
assets_to_push = []
system_user_assets = system_user.assets.all()
if assets:
assets_to_push.extend(assets)
if asset_groups:
for group in asset_groups:
assets_to_push.extend(group.assets.all())
assets_need_push = set(assets_to_push) - set(system_user_assets)
if not assets_need_push:
return
logger.debug("Push system user {} to {} assets".format(
system_user.name, ', '.join([asset.hostname for asset in assets_need_push])
))
result = push_system_user(system_user, assets_need_push, PUSH_SYSTEM_USER_TASK_NAME)
system_user.assets.add(*tuple(assets_need_push))
return result
This diff is collapsed.
......@@ -15,6 +15,7 @@
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset num' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
......@@ -34,11 +35,7 @@ $(document).ready(function(){
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 4, createdCell: function (td, cellData) {
var innerHtml = cellData.length > 8 ? cellData.substring(0, 24) + '...': cellData;
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
{targets: 6, createdCell: function (td, cellData, rowData) {
{# var script_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);#}
var update_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
......@@ -47,7 +44,7 @@ $(document).ready(function(){
}}],
ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
{data: "comment" }, {data: "id" }],
{data: "unreachable_amount"}, {data: "comment" }, {data: "id" }]
};
jumpserver.initDataTable(options);
})
......
......@@ -58,18 +58,16 @@
<td>{{ asset.ip }}</td>
<td>{{ asset.port }}</td>
<td>
<i class="fa fa-check text-navy"></i>
</td>
<td>
<button class="btn btn-danger pull-right btn-xs {% if asset.is_inherit_from_asset_groups %} disabled {% endif %}" type="button"><i class="fa fa-minus"></i></button>
<button class="btn btn-danger pull-right btn-xs" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{# <div class="row">#}
{# {% include '_pagination.html' %}#}
{# </div>#}
</div>
</div>
</div>
......
......@@ -60,7 +60,7 @@ $(document).ready(function(){
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [{data: "id" }, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () { return "3"}},
columns: [{data: "id" }, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: "unreachable_amount"},
{data: "comment" }, {data: "id" }],
op_html: $('#actions').html()
};
......
......@@ -41,6 +41,7 @@ urlpatterns = [
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]+)/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]+)/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]+)/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]+)/assets/$', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
# Resource system user url
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'),
......
# ~*~ coding: utf-8 ~*~
#
from .models import Asset
def test_admin_user_connective_manual(asset):
from ops.utils import run_AdHoc
if not isinstance(asset, list):
asset = [asset]
task_tuple = (
('ping', ''),
)
summary, _ = run_AdHoc(task_tuple, asset, record=False)
if len(summary['failed']) != 0:
return False
else:
return True
from common.utils import get_object_or_none
from .models import Asset, SystemUser
def get_assets_by_id_list(id_list):
......@@ -25,26 +12,6 @@ def get_assets_by_hostname_list(hostname_list):
return Asset.objects.filter(hostname__in=hostname_list)
def get_asset_admin_user(user, asset):
if user.is_superuser:
return asset.admin_user
else:
msg = "{} have no permission for admin user".format(user.username)
raise PermissionError(msg)
def get_asset_system_user(user, asset, system_user_name):
from perms.utils import get_user_granted_assets
assets = get_user_granted_assets(user)
system_users = {system_user.name: system_user for system_user in assets.get(asset)}
if system_user_name in system_users:
return system_users[system_user_name]
else:
msg = "{} have no permission for {}".format(user.name, system_user_name)
raise PermissionError(msg)
def get_assets_with_admin_by_hostname_list(hostname_list):
assets = Asset.objects.filter(hostname__in=hostname_list)
return [(asset, asset.admin_user) for asset in assets]
def get_system_user_by_name(name):
system_user = get_object_or_none(SystemUser, name=name)
return system_user
......@@ -14,7 +14,7 @@ from ..hands import AdminUserRequiredMixin
__all__ = ['AdminUserCreateView', 'AdminUserDetailView',
'AdminUserDeleteView', 'AdminUserListView',
'AdminUserUpdateView',
'AdminUserUpdateView', 'AdminUserAssetsView',
]
......@@ -104,6 +104,31 @@ class AdminUserDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
return super(AdminUserDetailView, self).get_context_data(**kwargs)
class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
template_name = 'assets/admin_user_assets.html'
context_object_name = 'admin_user'
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AdminUser.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
self.queryset = self.object.assets.all()
sorted(self.queryset, key=lambda x: x.is_connective() is False)
return self.queryset
def get_context_data(self, **kwargs):
context = {
'app': 'assets',
'action': 'Admin user detail',
"total_amount": len(self.queryset),
'unreachable_amount': len([asset for asset in self.queryset if asset.is_connective() is False])
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
model = AdminUser
template_name = 'assets/delete_confirm.html'
......
......@@ -117,25 +117,18 @@ class SystemUserAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=SystemUser.objects.all())
return super(SystemUserAssetView, self).get(request, *args, **kwargs)
return super().get(request, *args, **kwargs)
def get_asset_groups(self):
return self.object.asset_groups.all()
# Todo: queryset default order by connectivity, need ops support
def get_queryset(self):
return list(self.object.get_assets())
return self.object.assets.all()
def get_context_data(self, **kwargs):
asset_groups = self.get_asset_groups()
assets = self.get_queryset()
context = {
'app': 'assets',
'action': 'System user asset',
'assets_remain': [asset for asset in Asset.objects.all() if asset not in assets],
'asset_groups': asset_groups,
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
if asset_group not in asset_groups]
'asset_groups': AssetGroup.objects.all(),
}
kwargs.update(context)
return super(SystemUserAssetView, self).get_context_data(**kwargs)
......
......@@ -27,8 +27,8 @@ app.conf.update(
'schedule': 60*60*60*24,
'args': (),
},
'test-admin-user-connective': {
'task': 'assets.tasks.test_admin_user_connective_period',
'test-admin-user-connectability_periode': {
'task': 'assets.tasks.test_admin_user_connectability_period',
'schedule': 60*60*60,
'args': (),
},
......
......@@ -15,6 +15,7 @@ from email.utils import formatdate
import calendar
import threading
from six import StringIO
import uuid
import paramiko
import sshpubkeys
......@@ -378,4 +379,8 @@ def sum_capacity(cap_list):
return capacity_convert(total, expect='auto')
def get_short_uuid_str():
return str(uuid.uuid4()).split('-')[-1]
signer = Signer()
......@@ -299,6 +299,7 @@ REST_FRAMEWORK = {
'users.authentication.SessionAuthentication',
),
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S',
}
AUTHENTICATION_BACKENDS = [
......
......@@ -5,21 +5,21 @@ from ansible.plugins.callback import CallbackBase
class AdHocResultCallback(CallbackBase):
"""
AdHoc result Callback
Task result Callback
"""
def __init__(self, display=None):
# result_raw example: {
# "ok": {"hostname": [{"task_name": {},...],..},
# "failed": {"hostname": ["task_name": {}..], ..},
# "unreachable: {"hostname": ["task_name": {}, ..]},
# "skipped": {"hostname": ["task_name": {}, ..], ..},
# "ok": {"hostname": {"task_name": {},...},..},
# "failed": {"hostname": {"task_name": {}..}, ..},
# "unreachable: {"hostname": {"task_name": {}, ..}},
# "skipped": {"hostname": {"task_name": {}, ..}, ..},
# }
# results_summary example: {
# "contacted": {"hostname",...},
# "dark": {"hostname": [{"task_name": "error"},...],},
# "dark": {"hostname": {"task_name": {}, "task_name": {}},...,},
# }
self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={})
self.results_summary = dict(contacted=set(), dark={})
self.results_summary = dict(contacted=[], dark={})
super().__init__(display)
def gather_result(self, t, res):
......@@ -28,23 +28,24 @@ class AdHocResultCallback(CallbackBase):
task_result = res._result
if self.results_raw[t].get(host):
self.results_raw[t][host].append({task_name: task_result})
self.results_raw[t][host][task_name] = task_result
else:
self.results_raw[t][host] = [{task_name: task_result}]
self.results_raw[t][host] = {task_name: task_result}
self.clean_result(t, host, task_name, task_result)
def clean_result(self, t, host, task_name, task_result):
contacted = self.results_summary["contacted"]
dark = self.results_summary["dark"]
if t in ("ok", "skipped") and host not in dark:
contacted.add(host)
if host not in contacted:
contacted.append(host)
else:
if dark.get(host):
dark[host].append({task_name: task_result})
dark[host][task_name] = task_result
else:
dark[host] = [{task_name: task_result}]
dark[host] = {task_name: task_result}
if host in contacted:
contacted.remove(dark)
contacted.remove(host)
def v2_runner_on_failed(self, result, ignore_errors=False):
self.gather_result("failed", result)
......
# ~*~ coding: utf-8 ~*~
from ansible.inventory.group import Group
from ansible.inventory.host import Host
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.parsing.dataloader import DataLoader
class JMSHost(Host):
__all__ = [
'BaseHost', 'BaseInventory'
]
class BaseHost(Host):
def __init__(self, host_data):
"""
初始化
......@@ -14,6 +18,7 @@ class JMSHost(Host):
"hostname": "",
"ip": "",
"port": "",
# behind is not must be required
"username": "",
"password": "",
"private_key": "",
......@@ -29,7 +34,7 @@ class JMSHost(Host):
self.host_data = host_data
hostname = host_data.get('hostname') or host_data.get('ip')
port = host_data.get('port') or 22
super(JMSHost, self).__init__(hostname, port)
super().__init__(hostname, port)
self.__set_required_variables()
self.__set_extra_variables()
......@@ -37,7 +42,9 @@ class JMSHost(Host):
host_data = self.host_data
self.set_variable('ansible_host', host_data['ip'])
self.set_variable('ansible_port', host_data['port'])
self.set_variable('ansible_user', host_data['username'])
if host_data.get('username'):
self.set_variable('ansible_user', host_data['username'])
# 添加密码和秘钥
if host_data.get('password'):
......@@ -63,30 +70,15 @@ class JMSHost(Host):
return self.name
class JMSInventory(InventoryManager):
class BaseInventory(InventoryManager):
"""
提供生成Ansible inventory对象的方法
"""
loader_class = DataLoader
variable_manager_class = VariableManager
host_manager_class = JMSHost
host_manager_class = BaseHost
def __init__(self, host_list=None):
if host_list is None:
host_list = []
self.host_list = host_list
assert isinstance(host_list, list)
self.loader = self.loader_class()
self.variable_manager = self.variable_manager_class()
super().__init__(self.loader)
def get_groups(self):
return self._inventory.groups
def get_group(self, name):
return self._inventory.groups.get(name, None)
def parse_sources(self, cache=False):
"""
用于生成动态构建Ansible Inventory. super().__init__ 会自动调用
host_list: [{
......@@ -105,9 +97,23 @@ class JMSInventory(InventoryManager):
"vars": {},
},
]
:return: None
:param host_list:
"""
if host_list is None:
host_list = []
self.host_list = host_list
assert isinstance(host_list, list)
self.loader = self.loader_class()
self.variable_manager = self.variable_manager_class()
super().__init__(self.loader)
def get_groups(self):
return self._inventory.groups
def get_group(self, name):
return self._inventory.groups.get(name, None)
def parse_sources(self, cache=False):
group_all = self.get_group('all')
ungrouped = self.get_group('ungrouped')
......@@ -119,9 +125,14 @@ class JMSInventory(InventoryManager):
for group_name in groups_data:
group = self.get_group(group_name)
if group is None:
group = Group(group_name)
self.add_group(group)
self.add_group(group_name)
group = self.get_group(group_name)
group.add_host(host)
else:
ungrouped.add_host(host)
group_all.add_host(host)
def get_matched_hosts(self, pattern):
return self.get_hosts(pattern)
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
import os
from collections import namedtuple
......@@ -11,7 +10,6 @@ from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.playbook.play import Play
import ansible.constants as C
from .inventory import JMSInventory
from .callback import AdHocResultCallback, PlaybookResultCallBack, \
CommandResultCallback
from common.utils import get_logger
......@@ -71,36 +69,19 @@ class PlayBookRunner:
# Default results callback
results_callback_class = PlaybookResultCallBack
inventory_class = JMSInventory
loader_class = DataLoader
variable_manager_class = VariableManager
options = get_default_options()
def __init__(self, hosts=None, options=None):
def __init__(self, inventory=None, options=None):
"""
:param options: Ansible options like ansible.cfg
:param hosts: [
{
"hostname": "",
"ip": "",
"port": "",
"username": "",
"password": "",
"private_key": "",
"become": {
"method": "",
"user": "",
"pass": "",
},
"groups": [],
"vars": {},
},
]
:param inventory: Ansible inventory
"""
if options:
self.options = options
C.RETRY_FILES_ENABLED = False
self.inventory = self.inventory_class(hosts)
self.inventory = inventory
self.loader = self.loader_class()
self.results_callback = self.results_callback_class()
self.playbook_path = options.playbook_path
......@@ -141,20 +122,19 @@ class AdHocRunner:
ADHoc Runner接口
"""
results_callback_class = AdHocResultCallback
inventory_class = JMSInventory
loader_class = DataLoader
variable_manager_class = VariableManager
options = get_default_options()
default_options = get_default_options()
def __init__(self, hosts, options=None):
def __init__(self, inventory, options=None):
if options:
self.options = options
self.pattern = ''
self.inventory = inventory
self.loader = DataLoader()
self.inventory = self.inventory_class(hosts)
self.variable_manager = VariableManager(loader=self.loader, inventory=self.inventory)
self.variable_manager = VariableManager(
loader=self.loader, inventory=self.inventory
)
@staticmethod
def check_module_args(module_name, module_args=''):
......@@ -163,14 +143,22 @@ class AdHocRunner:
raise AnsibleError(err)
def check_pattern(self, pattern):
if not pattern:
raise AnsibleError("Pattern `{}` is not valid!".format(pattern))
if not self.inventory.list_hosts("all"):
raise AnsibleError("Inventory is empty.")
if not self.inventory.list_hosts(pattern):
raise AnsibleError(
"pattern: %s dose not match any hosts." % pattern
)
def clean_tasks(self, tasks):
cleaned_tasks = []
for task in tasks:
self.check_module_args(task['action']['module'], task['action'].get('args'))
cleaned_tasks.append(task)
return cleaned_tasks
def set_option(self, k, v):
kwargs = {k: v}
self.options = self.options._replace(**kwargs)
......@@ -182,17 +170,15 @@ class AdHocRunner:
:param play_name: The play name
:return:
"""
self.check_pattern(pattern)
results_callback = self.results_callback_class()
clean_tasks = []
for task in tasks:
self.check_module_args(task['action']['module'], task['action'].get('args'))
clean_tasks.append(task)
cleaned_tasks = self.clean_tasks(tasks)
play_source = dict(
name=play_name,
hosts=pattern,
gather_facts=gather_facts,
tasks=clean_tasks
tasks=cleaned_tasks
)
play = Play().load(
......@@ -209,6 +195,9 @@ class AdHocRunner:
stdout_callback=results_callback,
passwords=self.options.passwords,
)
logger.debug("Get inventory matched hosts: {}".format(
self.inventory.get_matched_hosts(pattern)
))
try:
tqm.run(play)
......
......@@ -6,7 +6,7 @@ import unittest
sys.path.insert(0, '../..')
from ops.ansible.inventory import JMSInventory
from ops.ansible.inventory import BaseInventory
class TestJMSInventory(unittest.TestCase):
......@@ -41,7 +41,7 @@ class TestJMSInventory(unittest.TestCase):
"vars": {"love": "yes"},
}]
self.inventory = JMSInventory(host_list=host_list)
self.inventory = BaseInventory(host_list=host_list)
def test_hosts(self):
print("#"*10 + "Hosts" + "#"*10)
......
......@@ -7,6 +7,7 @@ import sys
sys.path.insert(0, "../..")
from ops.ansible.runner import AdHocRunner, CommandRunner
from ops.ansible.inventory import BaseInventory
class TestAdHocRunner(unittest.TestCase):
......@@ -20,7 +21,8 @@ class TestAdHocRunner(unittest.TestCase):
"password": "redhat",
},
]
self.runner = AdHocRunner(hosts=host_data)
inventory = BaseInventory(host_data)
self.runner = AdHocRunner(inventory)
def test_run(self):
tasks = [
......@@ -43,7 +45,8 @@ class TestCommandRunner(unittest.TestCase):
"password": "redhat",
},
]
self.runner = CommandRunner(hosts=host_data)
inventory = BaseInventory(host_data)
self.runner = CommandRunner(inventory)
def test_execute(self):
res = self.runner.execute('ls', 'all')
......
# ~*~ coding: utf-8 ~*~
from django.shortcuts import get_object_or_404
from rest_framework import viewsets
from .hands import IsSuperUser
from .models import AdHoc
from .serializers import TaskSerializer
from .models import Task, AdHoc, AdHocRunHistory
from .serializers import TaskSerializer, AdHocSerializer, AdHocRunHistorySerializer
class TaskViewSet(viewsets.ModelViewSet):
queryset = AdHoc.objects.all()
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = (IsSuperUser,)
class AdHocViewSet(viewsets.ModelViewSet):
queryset = AdHoc.objects.all()
serializer_class = AdHocSerializer
permission_classes = (IsSuperUser,)
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 AdHocRunHistorySet(viewsets.ModelViewSet):
queryset = AdHocRunHistory.objects.all()
serializer_class = AdHocRunHistorySerializer
permission_classes = (IsSuperUser,)
def get_queryset(self):
task_id = self.request.query_params.get('task')
if task_id:
task = get_object_or_404(Task, id=task_id)
adhocs = task.adhoc.all()
self.queryset = self.queryset.filter(adhoc__in=adhocs)
return self.queryset
# -*- coding: utf-8 -*-
#
from .ansible.inventory import BaseInventory
from assets.utils import get_assets_by_hostname_list, get_system_user_by_name
__all__ = [
'JMSInventory'
]
class JMSInventory(BaseInventory):
"""
JMS Inventory is the manager with jumpserver assets, so you can
write you own manager, construct you inventory
"""
def __init__(self, hostname_list, run_as_admin=False, run_as=None, become_info=None):
self.hostname_list = hostname_list
self.using_admin = run_as_admin
self.run_as = run_as
self.become_info = become_info
assets = self.get_jms_assets()
if run_as_admin:
host_list = [asset._to_secret_json() for asset in assets]
else:
host_list = [asset.to_json() for asset in assets]
if run_as:
run_user_info = self.get_run_user_info()
for host in host_list:
host.update(run_user_info)
if become_info:
for host in host_list:
host.update(become_info)
super().__init__(host_list=host_list)
def get_jms_assets(self):
assets = get_assets_by_hostname_list(self.hostname_list)
return assets
def get_run_user_info(self):
system_user = get_system_user_by_name(self.run_as)
if not system_user:
return {}
else:
return system_user._to_secret_json()
......@@ -6,20 +6,25 @@ import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.utils import signer
__all__ = ["AdHoc", "AdHocRunHistory"]
__all__ = ["Task", "AdHoc", "AdHocRunHistory"]
logger = logging.getLogger(__name__)
class AdHoc(models.Model):
class Task(models.Model):
"""
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
"""
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
is_deleted = models.BooleanField(default=False)
created_by = models.CharField(max_length=128, blank=True, default='')
date_create = models.DateTimeField(auto_now_add=True)
date_created = models.DateTimeField(auto_now_add=True)
@property
def short_id(self):
......@@ -28,24 +33,48 @@ class AdHoc(models.Model):
def __str__(self):
return self.name
def get_latest_adhoc(self):
return self.adhoc.all().order_by('date_created').last()
def get_latest_history(self):
return self.get_latest_adhoc().get_latest_history()
def get_all_run_history(self):
adhocs = self.adhoc.all()
return AdHocRunHistory.objects.filter(adhoc__in=adhocs)
def get_all_run_times(self):
history_all = self.get_all_run_history()
total = len(history_all)
success = len([history for history in history_all if history.is_success])
failed = len([history for history in history_all if not history.is_success])
return {'total': total, 'success': success, 'failed': failed}
class AdHocData(models.Model):
BECOME_METHOD_CHOICES = (
('sudo', 'sudo'),
('su', 'su'),
)
version = models.UUIDField(default=uuid.uuid4, primary_key=True)
subject = models.ForeignKey(AdHoc, on_delete=models.CASCADE)
_tasks = models.TextField(verbose_name=_('Tasks')) # [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ]
class Meta:
db_table = 'ops_task'
class AdHoc(models.Model):
"""
task: A task reference
_tasks: [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ]
_options: ansible options, more see ops.ansible.runner.Options
_hosts: ["hostname1", "hostname2"], hostname must be unique key of cmdb
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: if not run as admin, it run it as a system/common user from cmdb
_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 = models.TextField(verbose_name=_('Tasks'))
pattern = models.CharField(max_length=64, default='', verbose_name=_('Pattern'))
_options = models.CharField(max_length=1024, default='', verbose_name=_('Options'))
_hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2']
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
run_as = models.CharField(max_length=128, verbose_name=_("Run as"))
become = models.BooleanField(default=False, verbose_name=_("Become"))
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64)
_become_pass = models.CharField(default='', max_length=128)
pattern = models.CharField(max_length=64, default='', verbose_name=_('Pattern'))
created_by = models.CharField(max_length=64, verbose_name=_('Create by'))
run_as = models.CharField(max_length=128, default='', verbose_name=_("Run as"))
_become = models.CharField(max_length=1024, default='', verbose_name=_("Become"))
created_by = models.CharField(max_length=64, default='', verbose_name=_('Create by'))
date_created = models.DateTimeField(auto_now_add=True)
@property
......@@ -54,7 +83,10 @@ class AdHocData(models.Model):
@tasks.setter
def tasks(self, item):
self._tasks = json.dumps(item)
if item and isinstance(item, list):
self._tasks = json.dumps(item)
else:
raise SyntaxError('Tasks should be a list')
@property
def hosts(self):
......@@ -65,42 +97,83 @@ class AdHocData(models.Model):
self._hosts = json.dumps(item)
@property
def become_pass(self):
return signer.unsign(self._become_pass)
def become(self):
if self._become:
return json.loads(signer.unsign(self._become))
else:
return {}
@become.setter
def become(self, item):
"""
:param item: {
method: "sudo",
user: "user",
pass: "pass",
}
:return:
"""
self._become = signer.sign(json.dumps(item))
@property
def options(self):
if self._options:
return json.loads(self._options)
else:
return {}
@become_pass.setter
def become_pass(self, password):
self._become_pass = signer.sign(password)
@options.setter
def options(self, item):
self._options = json.dumps(item)
@property
def short_version(self):
return str(self.version).split('-')[-1]
def short_id(self):
return str(self.id).split('-')[-1]
def run(self):
pass
def get_latest_history(self):
return self.history.all().order_by('date_start').last()
def __str__(self):
return "{} of {}".format(self.subject.name, self.short_version)
return "{} of {}".format(self.task.name, self.short_id)
class Meta:
db_table = "ops_adhoc_data"
db_table = "ops_adhoc"
class AdHocRunHistory(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, primary_key=True)
adhoc = models.ForeignKey(AdHocData, on_delete=models.CASCADE)
"""
AdHoc running history.
"""
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
adhoc = models.ForeignKey(AdHoc, related_name='history', on_delete=models.CASCADE)
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 = models.TextField(blank=True, null=True, verbose_name=_('Playbook raw result'))
summary = models.TextField(blank=True, null=True, verbose_name=_('Playbook summary'))
_result = models.TextField(blank=True, null=True, verbose_name=_('Adhoc raw result'))
_summary = models.TextField(blank=True, null=True, verbose_name=_('Adhoc result summary'))
@property
def short_id(self):
return str(self.id).split('-')[-1]
@property
def result(self):
return json.loads(self._result)
@result.setter
def result(self, item):
self._result = json.dumps(item)
@property
def summary(self):
return json.loads(self._summary)
@summary.setter
def summary(self, item):
self._summary = json.dumps(item)
def __str__(self):
return self.short_id
......
......@@ -2,12 +2,43 @@
from __future__ import unicode_literals
from rest_framework import serializers
from .models import AdHoc
from .models import Task, AdHoc, AdHocRunHistory
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = AdHoc
model = Task
fields = '__all__'
class AdHocSerializer(serializers.ModelSerializer):
class Meta:
model = AdHoc
exclude = ('_tasks', '_options', '_hosts', '_become')
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['tasks', 'options', 'hosts', 'become', 'short_id'])
return fields
class AdHocRunHistorySerializer(serializers.ModelSerializer):
task = serializers.SerializerMethodField()
adhoc_short_id = serializers.SerializerMethodField()
class Meta:
model = AdHocRunHistory
exclude = ('_result', '_summary')
@staticmethod
def get_adhoc_short_id(obj):
return obj.adhoc.short_id
@staticmethod
def get_task(obj):
return obj.adhoc.task.id
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['summary', 'short_id'])
return fields
# coding: utf-8
from __future__ import absolute_import, unicode_literals
from celery import shared_task
from common.utils import get_logger
from .utils import run_AdHoc
from .utils import run_adhoc
logger = get_logger(__file__)
def rerun_task():
pass
@shared_task
def rerun_task(task_id):
from .models import Playbook
record = Playbook.objects.get(uuid=task_id)
assets = record.assets_json
task_tuple = record.module_args
pattern = record.pattern
task_name = record.name
return run_AdHoc(task_tuple, assets, pattern=pattern,
task_name=task_name, task_id=task_id)
def run_add_hoc_and_record_async(adhoc, **options):
return run_adhoc(adhoc, **options)
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'ops:task-detail' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task detail' %} </a>
</li>
<li class="active">
<a href="{% url 'ops:task-adhoc' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task versions' %} </a>
</li>
<li>
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'Versions of ' %} <b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-hover " id="task-version-list-table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th>{% trans 'Version' %}</th>
<th>{% trans 'Hosts' %}</th>
<th>{% trans 'Pattern' %}</th>
<th>{% trans 'Run as' %}</th>
<th>{% trans 'Become' %}</th>
<th>{% trans 'Datetime' %}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
var options = {
ele: $('#task-version-list-table'),
buttons: [],
order: [],
select: [],
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{# var detail_btn = '<a href="' + cellData + '</a>';#}
$(td).html(cellData);
}},
{targets: 2, createdCell: function (td, cellData, rowData) {
var dataLength = cellData.length;
$(td).html(dataLength);
}},
{targets: 4, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html("Admin")
} else {
$(td).html(cellData)
}
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
if (!cellData) {
$(td).html("")
} else {
$(td).html(cellData.user)
}
}}
],
ajax_url: '{% url "api-ops:adhoc-list" %}?task={{ object.pk }}',
columns: [{data: function(){return ""}}, {data: "short_id" }, {data: "hosts"}, {data: "pattern"},
{data: "run_as"}, {data: "become"}, {data: "date_created"}]
};
jumpserver.initDataTable(options);
})
</script>
{% endblock %}
......@@ -15,8 +15,14 @@
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task replay detail' %} </a>
<li class="active">
<a href="{% url 'ops:task-detail' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task detail' %} </a>
</li>
<li>
<a href="{% url 'ops:task-adhoc' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task versions' %} </a>
</li>
<li>
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
</li>
</ul>
</div>
......@@ -43,49 +49,47 @@
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="20%">{% trans 'UUID' %}:</td>
<td><b>{{ object.uuid }}</b></td>
<td width="20%">{% trans 'ID' %}:</td>
<td><b>{{ object.id }}</b></td>
</tr>
<tr>
<td width="20%">{% trans 'Name' %}:</td>
<td><b>{{ object.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Date start' %}:</td>
<td><b>{{ object.date_start }}</b></td>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ object.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Total versions' %}</td>
<td><b>{{ object.adhoc.all |length }}</b></td>
</tr>
<tr>
<td>{% trans 'Date finished' %}:</td>
<td><b>{{ object.date_finished }}</b></td>
<td>{% trans 'Last version' %}</td>
<td><b>{{ object.get_latest_adhoc.short_id }}</b></td>
</tr>
<tr>
<td>{% trans 'Latest run' %}:</td>
<td><b>{{ object.get_latest_history.date_start }}</b></td>
</tr>
<tr>
<td>{% trans 'Time delta' %}:</td>
<td><b>{{ object.timedelta}} s</b></td>
<td><b>{{ object.get_latest_history.timedelta|floatformat}} s</b></td>
</tr>
<tr>
<td>{% trans 'Is finished' %}:</td>
<td><b>{{ object.is_finished|yesno:"Yes,No,Unkown" }}</b></td>
<td><b>{{ object.get_latest_history.is_finished|yesno:"Yes,No,Unkown" }}</b></td>
</tr>
<tr>
<td>{% trans 'Is success ' %}:</td>
{% if object.is_finished %}
<td><b>{{ object.is_success|yesno:"Yes,No,Unkown" }}</b></td>
{% else %}
<td>
<div class="progress progress-striped active">
<div style="width: 50%" aria-valuemax="100" aria-valuemin="0" aria-valuenow="75" role="progressbar" class="progress-bar progress-bar-primary">
<span class="sr-only">40% Complete (success)</span>
</div>
</div>
</td>
{% endif %}
<td><b>{{ object.get_latest_history.is_success|yesno:"Yes,No,Unkown" }}</b></td>
</tr>
<tr>
<td>{% trans 'assets' %}:</td>
<td>{% trans 'Conents' %}:</td>
<td>
<b>
{% for asset in object.total_assets %}
{{ asset.hostname }} <br/>
{% for task in object.get_latest_adhoc.tasks %}
{{ task.name }} : {{ task.action.module }} <br/>
{% endfor %}
</b>
</td>
......@@ -94,31 +98,6 @@
</table>
</div>
</div>
<div class="ibox float-e-margins">
<div class="ibox-title">
<span><b>Result</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<pre>
{{ object.result }}
</pre>
</div>
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-danger">
......@@ -154,7 +133,7 @@
<div class="panel-body">
<table class="table">
<tbody>
{% for host in results.success %}
{% for host in object.get_latest_history.summary.contacted %}
{% if forloop.first %}
<tr class="no-borders-tr">
{% else %}
......
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'ops:task-detail' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task detail' %} </a>
</li>
<li>
<a href="{% url 'ops:task-adhoc' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task versions' %} </a>
</li>
<li class="active">
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'Versions of ' %} <b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-hover " id="task-history-list-table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th>{% trans 'Date start' %}</th>
<th>{% trans 'F/S/T' %}</th>
<th>{% trans 'Is finished' %}</th>
<th>{% trans 'Is success' %}</th>
<th>{% trans 'Time' %}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
var options = {
ele: $('#task-history-list-table'),
buttons: [],
order: [],
select: [],
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{# var detail_btn = '<a href="' + cellData + '</a>';#}
$(td).html(cellData);
}},
{# {targets: 2, createdCell: function (td, cellData, rowData) {#}
{# var dataLength = cellData.length;#}
{# $(td).html(dataLength);#}
{# }},#}
{targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 4, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 5, createdCell: function (td, cellData) {
if (cellData) {
$(td).html(cellData.toFixed(2) + ' s')
} else {
$(td).html("0" + ' s')
}
}}
],
ajax_url: '{% url "api-ops:history-list" %}?task={{ object.pk }}',
columns: [{data: function(){return ""}}, {data: "date_start"}, {data: "adhoc_short_id"},
{data: "adhoc_short_id"}, {data: "adhoc_short_id"}, {data: "timedelta"}]
};
jumpserver.initDataTable(options);
})
</script>
{% endblock %}
......@@ -22,7 +22,7 @@
</div>
</div>
<div class="input-group">
<input type="text" class="form-control input-sm" name="keyword" placeholder="Keyword" value="{{ keyword }}">
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
</div>
<div class="input-group">
<div class="input-group-btn">
......@@ -37,10 +37,11 @@
{% block table_head %}
<th class="text-center"></th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'F/S/T' %}</th>
<th class="text-center">{% trans 'Versions' %}</th>
<th class="text-center">{% trans 'Hosts' %}</th>
<th class="text-center">{% trans 'Success' %}</th>
<th class="text-center">{% trans 'Finished' %}</th>
<th class="text-center">{% trans 'Date start' %}</th>
<th class="text-center">{% trans 'Date' %}</th>
<th class="text-center">{% trans 'Time' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
{% endblock %}
......@@ -49,26 +50,23 @@
{% for object in task_list %}
<tr class="gradeX">
<td class="text-center"><input type="checkbox" class="cbx-term"> </td>
<td class="text-center"><a href="{% url 'ops:task-detail' pk=object.uuid %}">{{ object.name }}</a></td>
<td class="text-center">{{ object.total_assets|length }}</td>
<td class="text-center"><a href="{% url 'ops:task-detail' pk=object.id %}">{{ object.name }}</a></td>
<td class="text-center">
{% if object.is_success %}
<i class="fa fa-check text-navy"></i>
{% else %}
<i class="fa fa-times text-danger"></i>
{% endif %}
<span class="text-danger">{{ object.get_all_run_times.failed }}</span>/<span class="text-navy">{{ object.get_all_run_times.success}}</span>/{{ object.get_all_run_times.total}}
</td>
<td class="text-center">{{ object.adhoc.all | length}}</td>
<td class="text-center">{{ object.get_latest_adhoc.hosts | length}}</td>
<td class="text-center">
{% if object.is_finished %}
{% if object.get_latest_history.is_success %}
<i class="fa fa-check text-navy"></i>
{% else %}
<i class="fa fa-times text-danger"></i>
{% endif %}
</td>
<td class="text-center">{{ object.date_start }}</td>
<td class="text-center">{{ object.timedelta }} s</td>
<td class="text-center">{{ object.get_latest_history.date_start }}</td>
<td class="text-center">{{ object.get_latest_history.timedelta|floatformat }} s</td>
<td class="text-center">
<a href="{% url 'ops:task-run' pk=object.uuid %}" class="btn btn-xs btn-info">{% trans "Run again" %}</a>
<a href="{% url 'ops:task-run' pk=object.id %}" class="btn btn-xs btn-info">{% trans "Run" %}</a>
<a data-uid="{{ object.uuid }}" class="btn btn-xs btn-danger btn-del">{% trans "Delete" %}</a>
</td>
</tr>
......
......@@ -5,17 +5,16 @@ import sys
import os
from django.test import TestCase
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
from ops.models import AdHoc, AdHocData
from ops.utils import run_adhoc
from ops.models import Task, AdHoc
from ops.utils import run_adhoc_object
class TestRunAdHoc(TestCase):
def setUp(self):
adhoc = AdHoc(name="Test run adhoc")
adhoc = Task(name="Test run adhoc")
adhoc.save()
self.data = AdHocData(subject=adhoc, run_as_admin=True, pattern='all')
self.data = AdHoc(subject=adhoc, run_as_admin=True, pattern='all')
self.data.tasks = [
{'name': 'run ls', 'action': {'module': 'shell', 'args': 'ls'}},
{'name': 'echo ', 'action': {'module': 'shell', 'args': 'echo 123'}},
......
......@@ -7,6 +7,8 @@ from .. import api
router = DefaultRouter()
router.register(r'v1/tasks', api.TaskViewSet, 'task')
router.register(r'v1/adhoc', api.AdHocViewSet, 'adhoc')
router.register(r'v1/history', api.AdHocRunHistorySet, 'history')
urlpatterns = []
......
......@@ -11,5 +11,7 @@ urlpatterns = [
# TResource Task url
url(r'^task/$', views.TaskListView.as_view(), name='task-list'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]+)/$', views.TaskDetailView.as_view(), name='task-detail'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]+)/adhoc/$', views.TaskAdhocView.as_view(), name='task-adhoc'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]+)/history/$', views.TaskHistoryView.as_view(), name='task-history'),
url(r'^task/(?P<pk>[0-9a-zA-Z\-]+)/run/$', views.TaskRunView.as_view(), name='task-run'),
]
\ No newline at end of file
......@@ -4,20 +4,16 @@ import re
import time
from django.utils import timezone
from common.utils import get_logger, get_object_or_none
from .ansible import AdHocRunner
from common.utils import get_logger, get_object_or_none, get_short_uuid_str
from .ansible import AdHocRunner, CommandResultCallback
from .inventory import JMSInventory
from .ansible.exceptions import AnsibleError
from .models import AdHocRunHistory
from assets.utils import get_assets_by_hostname_list
from .models import AdHocRunHistory, Task, AdHoc
logger = get_logger(__file__)
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
def run_AdHoc():
pass
def is_uuid(s):
if UUID_PATTERN.match(s):
return True
......@@ -25,97 +21,133 @@ def is_uuid(s):
return False
def asset_to_dict(asset):
return asset.to_json()
def asset_to_dict_with_credential(asset):
return asset._to_secret_json()
def system_user_to_dict_with_credential(system_user):
return system_user._to_secret_json()
def get_hosts_with_admin(hostname_list):
assets = get_assets_by_hostname_list(hostname_list)
return [asset._to_secret_json for asset in assets]
def get_hosts(hostname_list):
assets = get_assets_by_hostname_list(hostname_list)
return [asset.to_json for asset in assets]
def get_run_user(name):
from assets.models import SystemUser
system_user = get_object_or_none(SystemUser, name=name)
if system_user is None:
return {}
def record_adhoc(func):
def _deco(adhoc, **options):
record = AdHocRunHistory(adhoc=adhoc)
time_start = time.time()
try:
result = func(adhoc, **options)
record.is_finished = True
if result.results_summary.get('dark'):
record.is_success = False
else:
record.is_success = True
record.result = result.results_raw
record.summary = result.results_summary
return result
finally:
record.date_finished = timezone.now()
record.timedelta = time.time() - time_start
record.save()
return _deco
def get_adhoc_inventory(adhoc):
if adhoc.become:
become_info = {
'become': {
adhoc.become
}
}
else:
return system_user._to_secret_json()
become_info = None
inventory = JMSInventory(
adhoc.hosts, run_as_admin=adhoc.run_as_admin,
run_as=adhoc.run_as, become_info=become_info
)
return inventory
def get_hosts_with_run_user(hostname_list, run_as):
hosts_dict = get_hosts(hostname_list)
system_user_dct = get_run_user(run_as)
for host in hosts_dict:
host.update(system_user_dct)
return hosts_dict
def get_inventory(hostname_list, run_as_admin=False, run_as=None, become_info=None):
return JMSInventory(
hostname_list, run_as_admin=run_as_admin,
run_as=run_as, become_info=become_info
)
def hosts_add_become(hosts, adhoc_data):
if adhoc_data.become:
become_data = {
"become": {
"method": adhoc_data.become_method,
"user": adhoc_data.become_user,
"pass": adhoc_data.become_pass,
}
}
for host in hosts:
host.update(become_data)
return hosts
def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info=None):
inventory = get_inventory(
hostname_list, run_as_admin=run_as_admin,
run_as=run_as, become_info=become_info
)
runner = AdHocRunner(inventory)
return runner
def run_adhoc(adhoc_data, **options):
@record_adhoc
def run_adhoc_object(adhoc, **options):
"""
:param adhoc_data: Instance of AdHocData
:param adhoc: Instance of AdHoc
:param options: ansible support option, like forks ...
:return:
"""
name = adhoc_data.subject.name
hostname_list = adhoc_data.hosts
if adhoc_data.run_as_admin:
hosts = get_hosts_with_admin(hostname_list)
else:
hosts = get_hosts_with_run_user(hostname_list, adhoc_data.run_as)
hosts_add_become(hosts, adhoc_data) # admin user 自带become
runner = AdHocRunner(hosts)
name = adhoc.task.name
inventory = get_adhoc_inventory(adhoc)
runner = AdHocRunner(inventory)
for k, v in options:
runner.set_option(k, v)
record = AdHocRunHistory(adhoc=adhoc_data)
time_start = time.time()
try:
result = runner.run(adhoc_data.tasks, adhoc_data.pattern, name)
record.is_finished = True
if result.results_summary.get('dark'):
record.is_success = False
else:
record.is_success = True
record.result = result.results_raw
record.summary = result.results_summary
result = runner.run(adhoc.tasks, adhoc.pattern, name)
return result
except AnsibleError as e:
logger.error("Failed run adhoc {}, {}".format(name, e))
raise
finally:
record.date_finished = timezone.now()
record.timedelta = time.time() - time_start
record.save()
def run_adhoc(hostname_list, pattern, tasks, name=None,
run_as_admin=False, run_as=None, become_info=None):
if name is None:
name = "Adhoc-task-{}-{}".format(
get_short_uuid_str(),
timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
)
inventory = get_inventory(
hostname_list, run_as_admin=run_as_admin,
run_as=run_as, become_info=become_info
)
runner = AdHocRunner(inventory)
return runner.run(tasks, pattern, play_name=name)
def create_and_run_adhoc(hostname_list, pattern, tasks, name=None,
run_as_admin=False, run_as=None, become_info=None):
if name is None:
name = "Adhoc-task-{}-{}".format(
get_short_uuid_str(),
timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
)
task = Task(name=name)
task.save()
adhoc = AdHoc(
task=task, pattern=pattern, name=name,
run_as_admin=run_as_admin, run_as=run_as
)
adhoc.hosts = hostname_list
adhoc.tasks = tasks
adhoc.become = become_info
adhoc.save()
def get_task_by_name(name):
task = get_object_or_none(Task, name=name)
return task
def create_task(name, created_by=""):
return Task.objects.create(name=name, created_by=created_by)
def create_adhoc(task, hosts, tasks, pattern='all', options=None,
run_as_admin=False, run_as="",
become_info=None, created_by=""):
adhoc = AdHoc(task=task, pattern=pattern, run_as_admin=run_as_admin,
run_as=run_as, created_by=created_by)
adhoc.hosts = hosts
adhoc.tasks = tasks
adhoc.options = options
adhoc.become = become_info
adhoc.save()
return adhoc
......@@ -9,40 +9,40 @@ from django.views.generic import ListView, DetailView, View
from django.utils import timezone
from django.shortcuts import redirect, reverse
from .models import AdHoc, AdHocData, AdHocRunHistory
from .models import Task, AdHoc, AdHocRunHistory
from ops.tasks import rerun_task
class TaskListView(ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
model = AdHoc
ordering = ('-date_start',)
model = Task
ordering = ('-date_created',)
context_object_name = 'task_list'
template_name = 'ops/task_list.html'
date_format = '%m/%d/%Y'
keyword = date_from_s = date_to_s = ''
def get_queryset(self):
date_now = timezone.localtime(timezone.now())
date_to_default = date_now.strftime(self.date_format)
date_from_default = (date_now - timezone.timedelta(7)) \
.strftime(self.date_format)
date_to_default = timezone.now()
date_from_default = timezone.now() - timezone.timedelta(7)
date_from_default_s = date_from_default.strftime(self.date_format)
date_to_default_s = date_to_default.strftime(self.date_format)
self.queryset = super(TaskListView, self).get_queryset()
self.queryset = super().get_queryset()
self.keyword = self.request.GET.get('keyword', '')
self.date_from_s = self.request.GET.get('date_from', date_from_default)
self.date_to_s = self.request.GET.get('date_to', date_to_default)
self.date_from_s = self.request.GET.get('date_from', date_from_default_s)
self.date_to_s = self.request.GET.get('date_to', date_to_default_s)
if self.date_from_s:
date_from = datetime.strptime(self.date_from_s, self.date_format)
date_from = date_from.replace(tzinfo=timezone.get_current_timezone())
self.queryset = self.queryset.filter(date_start__gt=date_from)
self.queryset = self.queryset.filter(date_created__gt=date_from)
if self.date_to_s:
date_to = timezone.datetime.strptime(
self.date_to_s + ' 23:59:59', '%m/%d/%Y %H:%M:%S')
date_to = date_to.replace(tzinfo=timezone.get_current_timezone())
self.queryset = self.queryset.filter(date_finished__lt=date_to)
self.queryset = self.queryset.filter(date_created__lt=date_to)
if self.keyword:
self.queryset = self.queryset.filter(
......@@ -63,17 +63,42 @@ class TaskListView(ListView):
class TaskDetailView(DetailView):
model = AdHocRunHistory
model = Task
template_name = 'ops/task_detail.html'
def get_context_data(self, **kwargs):
context = {
'app': 'Ops',
'action': 'Playbook record detail',
'results': json.loads(self.object.summary or '{}'),
'action': 'Task detail',
}
kwargs.update(context)
return super(TaskDetailView, self).get_context_data(**kwargs)
return super().get_context_data(**kwargs)
class TaskAdhocView(DetailView):
model = Task
template_name = 'ops/task_adhoc.html'
def get_context_data(self, **kwargs):
context = {
'app': 'Ops',
'action': 'Task versions',
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class TaskHistoryView(DetailView):
model = Task
template_name = 'ops/task_history.html'
def get_context_data(self, **kwargs):
context = {
'app': 'Ops',
'action': 'Task run history',
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class TaskRunView(View):
......
......@@ -3,46 +3,11 @@ from __future__ import absolute_import, unicode_literals
from celery import shared_task
from common.utils import get_logger, encrypt_password
from ops.utils import run_AdHoc
logger = get_logger(__file__)
@shared_task(bind=True)
def push_users(self, assets, users):
"""
user: {
name: 'web',
username: 'web',
shell: '/bin/bash',
password: '123123123',
public_key: 'string',
sudo: '/bin/whoami,/sbin/ifconfig'
}
"""
if isinstance(users, dict):
users = [users]
if isinstance(assets, dict):
assets = [assets]
task_tuple = []
for user in users:
# 添加用户, 设置公钥, 设置sudo
task_tuple.extend([
('user', 'name={} shell={} state=present password={}'.format(
user['username'], user.get('shell', '/bin/bash'),
encrypt_password(user.get('password', None)))),
('authorized_key', "user={} state=present key='{}'".format(
user['username'], user['public_key'])),
('lineinfile',
"dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format(
user['username'], user.get('sudo', '/sbin/ifconfig')
))
])
task_name = 'Push user {}'.format(','.join([user['name'] for user in users]))
task = run_AdHoc(task_tuple, assets, pattern='all',
task_name=task_name, task_id=self.request.id)
return task
pass
......@@ -52,7 +52,7 @@
<i class="fa fa-coffee"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Playbook' %}</a></li>
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Tasks' %}</a></li>
</ul>
</li>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment