Commit 0c9e24dc authored by ibuler's avatar ibuler

[Feture] 添加ops 页面

parent 18fd04d6
...@@ -26,12 +26,13 @@ from .hands import IsSuperUser, IsAppUser, IsValidUser, \ ...@@ -26,12 +26,13 @@ from .hands import IsSuperUser, IsAppUser, IsValidUser, \
get_user_granted_assets, push_users get_user_granted_assets, push_users
from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser
from . import serializers from . import serializers
from .tasks import update_assets_hardware_info from .tasks import update_assets_hardware_info, test_admin_user_connectability_manual
from .utils import test_admin_user_connective_manual
class AssetViewSet(IDInFilterMixin, BulkModelViewSet): 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() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
...@@ -195,7 +196,7 @@ class AssetAdminUserTestView(AssetRefreshHardwareView): ...@@ -195,7 +196,7 @@ class AssetAdminUserTestView(AssetRefreshHardwareView):
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk') asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id) 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: if result:
return Response('1') return Response('1')
else: else:
......
# -*- coding: utf-8 -*-
#
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
print("Import assets model")
from .user import AdminUser, SystemUser from .user import AdminUser, SystemUser
from .cluster import * from .cluster import *
from .group import * from .group import *
......
...@@ -7,6 +7,7 @@ import uuid ...@@ -7,6 +7,7 @@ import uuid
from django.db import models from django.db import models
import logging import logging
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from .cluster import Cluster from .cluster import Cluster
from .group import AssetGroup from .group import AssetGroup
...@@ -21,6 +22,7 @@ def get_default_cluster(): ...@@ -21,6 +22,7 @@ def get_default_cluster():
class Asset(models.Model): class Asset(models.Model):
# Todo: Move them to settings
STATUS_CHOICES = ( STATUS_CHOICES = (
('In use', _('In use')), ('In use', _('In use')),
('Out of use', _('Out of use')), ('Out of use', _('Out of use')),
...@@ -103,6 +105,9 @@ class Asset(models.Model): ...@@ -103,6 +105,9 @@ class Asset(models.Model):
'groups': [group.name for group in self.groups.all()], 'groups': [group.name for group in self.groups.all()],
} }
def is_connective(self):
return cache.get(self.hostname)
def _to_secret_json(self): def _to_secret_json(self):
""" """
Ansible use it create inventory Ansible use it create inventory
......
...@@ -19,7 +19,6 @@ logger = logging.getLogger(__name__) ...@@ -19,7 +19,6 @@ logger = logging.getLogger(__name__)
class AssetGroup(models.Model): class AssetGroup(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name')) 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')) 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')) date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
comment = models.TextField(blank=True, verbose_name=_('Comment')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
......
...@@ -201,21 +201,6 @@ class SystemUser(models.Model): ...@@ -201,21 +201,6 @@ class SystemUser(models.Model):
def public_key(self, public_key_raw): def public_key(self, public_key_raw):
self._public_key = signer.sign(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): def _to_secret_json(self):
"""Push system user use it""" """Push system user use it"""
return { return {
...@@ -232,10 +217,6 @@ class SystemUser(models.Model): ...@@ -232,10 +217,6 @@ class SystemUser(models.Model):
def assets_amount(self): def assets_amount(self):
return self.assets.count() return self.assets.count()
@property
def asset_group_amount(self):
return self.asset_groups.count()
def to_json(self): def to_json(self):
return { return {
'id': self.id, 'id': self.id,
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache from django.core.cache import cache
from rest_framework import viewsets, serializers, generics 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 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): class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
assets_amount = serializers.SerializerMethodField() assets_amount = serializers.SerializerMethodField()
...@@ -64,11 +64,20 @@ class ClusterUpdateAssetsSerializer(serializers.ModelSerializer): ...@@ -64,11 +64,20 @@ class ClusterUpdateAssetsSerializer(serializers.ModelSerializer):
class AdminUserSerializer(serializers.ModelSerializer): class AdminUserSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
unreachable_amount = serializers.SerializerMethodField()
class Meta: class Meta:
model = AdminUser model = AdminUser
fields = '__all__' 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): def get_field_names(self, declared_fields, info):
fields = super(AdminUserSerializer, self).get_field_names(declared_fields, info) fields = super(AdminUserSerializer, self).get_field_names(declared_fields, info)
fields.append('assets_amount') fields.append('assets_amount')
...@@ -76,10 +85,20 @@ class AdminUserSerializer(serializers.ModelSerializer): ...@@ -76,10 +85,20 @@ class AdminUserSerializer(serializers.ModelSerializer):
class SystemUserSerializer(serializers.ModelSerializer): class SystemUserSerializer(serializers.ModelSerializer):
unreachable_amount = serializers.SerializerMethodField()
class Meta: class Meta:
model = SystemUser model = SystemUser
exclude = ('_password', '_private_key', '_public_key') 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): def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
fields.extend(['assets_amount']) fields.extend(['assets_amount'])
...@@ -167,8 +186,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer): ...@@ -167,8 +186,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_system_users_join(obj): def get_system_users_join(obj):
return ', '.join([system_user.username return ', '.join([system_user.username for system_user in obj.system_users_granted])
for system_user in obj.system_users_granted])
class MyAssetGrantedSerializer(AssetGrantedSerializer): class MyAssetGrantedSerializer(AssetGrantedSerializer):
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from celery import shared_task
import json import json
from celery import shared_task
from django.core.cache import cache from django.core.cache import cache
from ops.tasks import run_AdHoc from assets.models import SystemUser, AdminUser
from common.utils import get_object_or_none, capacity_convert, sum_capacity from common.utils import get_object_or_none, capacity_convert, sum_capacity, encrypt_password, get_logger
from .models import Asset 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 @shared_task
def update_assets_hardware_info(assets): def update_assets_hardware_info(assets):
task_tuple = ( """
('setup', ''), Using ansible api to update asset hardware info
) :param assets: asset seq
summary, result = run_AdHoc(task_tuple, assets, record=False) :return: result summary ['contacted': {}, 'dark': {}]
for hostname, info in result['contacted'].items(): """
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: if info:
info = info[0]['ansible_facts'] info = info[name]['ansible_facts']
else: else:
continue continue
asset = get_object_or_none(Asset, hostname=hostname) asset = get_object_or_none(Asset, hostname=hostname)
...@@ -58,23 +80,193 @@ def update_assets_hardware_info(assets): ...@@ -58,23 +80,193 @@ def update_assets_hardware_info(assets):
@shared_task @shared_task
def update_assets_hardware_period(): def update_assets_hardware_period():
"""
Update asset hardware period task
:return:
"""
assets = Asset.objects.filter(type__in=['Server', 'VM']) assets = Asset.objects.filter(type__in=['Server', 'VM'])
update_assets_hardware_info(assets) update_assets_hardware_info(assets)
@shared_task @shared_task
def test_admin_user_connective_period(): def test_admin_user_connectability(admin_user):
assets = Asset.objects.filter(type__in=['Server', 'VM']) """
task_tuple = ( Test asset admin user can connect or not. Using ansible api do that
('ping', ''), :param admin_user:
) :return:
summary, _ = run_AdHoc(task_tuple, assets, record=False) """
for i in summary['success']: from ops.utils import run_adhoc
cache.set(i, '1', 2*60*60*60) assets = admin_user.assets.all()
# assets = Asset.objects.filter(type__in=['Server', 'VM'])
for i, msg in summary['failed']: hosts = [asset.hostname for asset in assets]
cache.set(i, '0', 60*60*60) tasks = [
return summary {
"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 @@ ...@@ -15,6 +15,7 @@
<th class="text-center">{% trans 'Name' %}</th> <th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th> <th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset num' %}</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 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
...@@ -34,11 +35,7 @@ $(document).ready(function(){ ...@@ -34,11 +35,7 @@ $(document).ready(function(){
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=99991937 %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id)); $(td).html(detail_btn.replace('99991937', rowData.id));
}}, }},
{targets: 4, createdCell: function (td, cellData) { {targets: 6, createdCell: function (td, cellData, rowData) {
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) {
{# 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 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 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); 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(){ ...@@ -47,7 +44,7 @@ $(document).ready(function(){
}}], }}],
ajax_url: '{% url "api-assets:admin-user-list" %}', ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" }, 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); jumpserver.initDataTable(options);
}) })
......
...@@ -58,18 +58,16 @@ ...@@ -58,18 +58,16 @@
<td>{{ asset.ip }}</td> <td>{{ asset.ip }}</td>
<td>{{ asset.port }}</td> <td>{{ asset.port }}</td>
<td> <td>
<i class="fa fa-check text-navy"></i> <i class="fa fa-check text-navy"></i>
</td> </td>
<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> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{# <div class="row">#}
{# {% include '_pagination.html' %}#}
{# </div>#}
</div> </div>
</div> </div>
</div> </div>
......
...@@ -60,7 +60,7 @@ $(document).ready(function(){ ...@@ -60,7 +60,7 @@ $(document).ready(function(){
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
}}], }}],
ajax_url: '{% url "api-assets:system-user-list" %}', 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" }], {data: "comment" }, {data: "id" }],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -41,6 +41,7 @@ urlpatterns = [ ...@@ -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\-]+)/$', 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\-]+)/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\-]+)/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 # Resource system user url
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'), url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'),
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
from .models import Asset from common.utils import get_object_or_none
from .models import Asset, SystemUser
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
def get_assets_by_id_list(id_list): def get_assets_by_id_list(id_list):
...@@ -25,26 +12,6 @@ def get_assets_by_hostname_list(hostname_list): ...@@ -25,26 +12,6 @@ def get_assets_by_hostname_list(hostname_list):
return Asset.objects.filter(hostname__in=hostname_list) return Asset.objects.filter(hostname__in=hostname_list)
def get_asset_admin_user(user, asset): def get_system_user_by_name(name):
if user.is_superuser: system_user = get_object_or_none(SystemUser, name=name)
return asset.admin_user return system_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]
...@@ -14,7 +14,7 @@ from ..hands import AdminUserRequiredMixin ...@@ -14,7 +14,7 @@ from ..hands import AdminUserRequiredMixin
__all__ = ['AdminUserCreateView', 'AdminUserDetailView', __all__ = ['AdminUserCreateView', 'AdminUserDetailView',
'AdminUserDeleteView', 'AdminUserListView', 'AdminUserDeleteView', 'AdminUserListView',
'AdminUserUpdateView', 'AdminUserUpdateView', 'AdminUserAssetsView',
] ]
...@@ -104,6 +104,31 @@ class AdminUserDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView): ...@@ -104,6 +104,31 @@ class AdminUserDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
return super(AdminUserDetailView, self).get_context_data(**kwargs) 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): class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
model = AdminUser model = AdminUser
template_name = 'assets/delete_confirm.html' template_name = 'assets/delete_confirm.html'
......
...@@ -117,25 +117,18 @@ class SystemUserAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView): ...@@ -117,25 +117,18 @@ class SystemUserAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=SystemUser.objects.all()) 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): def get_queryset(self):
return list(self.object.get_assets()) return self.object.assets.all()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
asset_groups = self.get_asset_groups()
assets = self.get_queryset() assets = self.get_queryset()
context = { context = {
'app': 'assets', 'app': 'assets',
'action': 'System user asset', 'action': 'System user asset',
'assets_remain': [asset for asset in Asset.objects.all() if asset not in assets], 'assets_remain': [asset for asset in Asset.objects.all() if asset not in assets],
'asset_groups': asset_groups, 'asset_groups': AssetGroup.objects.all(),
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
if asset_group not in asset_groups]
} }
kwargs.update(context) kwargs.update(context)
return super(SystemUserAssetView, self).get_context_data(**kwargs) return super(SystemUserAssetView, self).get_context_data(**kwargs)
......
...@@ -27,8 +27,8 @@ app.conf.update( ...@@ -27,8 +27,8 @@ app.conf.update(
'schedule': 60*60*60*24, 'schedule': 60*60*60*24,
'args': (), 'args': (),
}, },
'test-admin-user-connective': { 'test-admin-user-connectability_periode': {
'task': 'assets.tasks.test_admin_user_connective_period', 'task': 'assets.tasks.test_admin_user_connectability_period',
'schedule': 60*60*60, 'schedule': 60*60*60,
'args': (), 'args': (),
}, },
......
...@@ -15,6 +15,7 @@ from email.utils import formatdate ...@@ -15,6 +15,7 @@ from email.utils import formatdate
import calendar import calendar
import threading import threading
from six import StringIO from six import StringIO
import uuid
import paramiko import paramiko
import sshpubkeys import sshpubkeys
...@@ -378,4 +379,8 @@ def sum_capacity(cap_list): ...@@ -378,4 +379,8 @@ def sum_capacity(cap_list):
return capacity_convert(total, expect='auto') return capacity_convert(total, expect='auto')
def get_short_uuid_str():
return str(uuid.uuid4()).split('-')[-1]
signer = Signer() signer = Signer()
...@@ -299,6 +299,7 @@ REST_FRAMEWORK = { ...@@ -299,6 +299,7 @@ REST_FRAMEWORK = {
'users.authentication.SessionAuthentication', 'users.authentication.SessionAuthentication',
), ),
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',), 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S',
} }
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
......
...@@ -5,21 +5,21 @@ from ansible.plugins.callback import CallbackBase ...@@ -5,21 +5,21 @@ from ansible.plugins.callback import CallbackBase
class AdHocResultCallback(CallbackBase): class AdHocResultCallback(CallbackBase):
""" """
AdHoc result Callback Task result Callback
""" """
def __init__(self, display=None): def __init__(self, display=None):
# result_raw example: { # result_raw example: {
# "ok": {"hostname": [{"task_name": {},...],..}, # "ok": {"hostname": {"task_name": {},...},..},
# "failed": {"hostname": ["task_name": {}..], ..}, # "failed": {"hostname": {"task_name": {}..}, ..},
# "unreachable: {"hostname": ["task_name": {}, ..]}, # "unreachable: {"hostname": {"task_name": {}, ..}},
# "skipped": {"hostname": ["task_name": {}, ..], ..}, # "skipped": {"hostname": {"task_name": {}, ..}, ..},
# } # }
# results_summary example: { # results_summary example: {
# "contacted": {"hostname",...}, # "contacted": {"hostname",...},
# "dark": {"hostname": [{"task_name": "error"},...],}, # "dark": {"hostname": {"task_name": {}, "task_name": {}},...,},
# } # }
self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={}) self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={})
self.results_summary = dict(contacted=set(), dark={}) self.results_summary = dict(contacted=[], dark={})
super().__init__(display) super().__init__(display)
def gather_result(self, t, res): def gather_result(self, t, res):
...@@ -28,23 +28,24 @@ class AdHocResultCallback(CallbackBase): ...@@ -28,23 +28,24 @@ class AdHocResultCallback(CallbackBase):
task_result = res._result task_result = res._result
if self.results_raw[t].get(host): 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: 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) self.clean_result(t, host, task_name, task_result)
def clean_result(self, t, host, task_name, task_result): def clean_result(self, t, host, task_name, task_result):
contacted = self.results_summary["contacted"] contacted = self.results_summary["contacted"]
dark = self.results_summary["dark"] dark = self.results_summary["dark"]
if t in ("ok", "skipped") and host not in dark: if t in ("ok", "skipped") and host not in dark:
contacted.add(host) if host not in contacted:
contacted.append(host)
else: else:
if dark.get(host): if dark.get(host):
dark[host].append({task_name: task_result}) dark[host][task_name] = task_result
else: else:
dark[host] = [{task_name: task_result}] dark[host] = {task_name: task_result}
if host in contacted: if host in contacted:
contacted.remove(dark) contacted.remove(host)
def v2_runner_on_failed(self, result, ignore_errors=False): def v2_runner_on_failed(self, result, ignore_errors=False):
self.gather_result("failed", result) self.gather_result("failed", result)
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from ansible.inventory.group import Group
from ansible.inventory.host import Host from ansible.inventory.host import Host
from ansible.vars.manager import VariableManager from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager from ansible.inventory.manager import InventoryManager
from ansible.parsing.dataloader import DataLoader from ansible.parsing.dataloader import DataLoader
class JMSHost(Host): __all__ = [
'BaseHost', 'BaseInventory'
]
class BaseHost(Host):
def __init__(self, host_data): def __init__(self, host_data):
""" """
初始化 初始化
...@@ -14,6 +18,7 @@ class JMSHost(Host): ...@@ -14,6 +18,7 @@ class JMSHost(Host):
"hostname": "", "hostname": "",
"ip": "", "ip": "",
"port": "", "port": "",
# behind is not must be required
"username": "", "username": "",
"password": "", "password": "",
"private_key": "", "private_key": "",
...@@ -29,7 +34,7 @@ class JMSHost(Host): ...@@ -29,7 +34,7 @@ class JMSHost(Host):
self.host_data = host_data self.host_data = host_data
hostname = host_data.get('hostname') or host_data.get('ip') hostname = host_data.get('hostname') or host_data.get('ip')
port = host_data.get('port') or 22 port = host_data.get('port') or 22
super(JMSHost, self).__init__(hostname, port) super().__init__(hostname, port)
self.__set_required_variables() self.__set_required_variables()
self.__set_extra_variables() self.__set_extra_variables()
...@@ -37,7 +42,9 @@ class JMSHost(Host): ...@@ -37,7 +42,9 @@ class JMSHost(Host):
host_data = self.host_data host_data = self.host_data
self.set_variable('ansible_host', host_data['ip']) self.set_variable('ansible_host', host_data['ip'])
self.set_variable('ansible_port', host_data['port']) 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'): if host_data.get('password'):
...@@ -63,30 +70,15 @@ class JMSHost(Host): ...@@ -63,30 +70,15 @@ class JMSHost(Host):
return self.name return self.name
class JMSInventory(InventoryManager): class BaseInventory(InventoryManager):
""" """
提供生成Ansible inventory对象的方法 提供生成Ansible inventory对象的方法
""" """
loader_class = DataLoader loader_class = DataLoader
variable_manager_class = VariableManager variable_manager_class = VariableManager
host_manager_class = JMSHost host_manager_class = BaseHost
def __init__(self, host_list=None): 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__ 会自动调用 用于生成动态构建Ansible Inventory. super().__init__ 会自动调用
host_list: [{ host_list: [{
...@@ -105,9 +97,23 @@ class JMSInventory(InventoryManager): ...@@ -105,9 +97,23 @@ class JMSInventory(InventoryManager):
"vars": {}, "vars": {},
}, },
] ]
:param host_list:
:return: 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):
group_all = self.get_group('all') group_all = self.get_group('all')
ungrouped = self.get_group('ungrouped') ungrouped = self.get_group('ungrouped')
...@@ -119,9 +125,14 @@ class JMSInventory(InventoryManager): ...@@ -119,9 +125,14 @@ class JMSInventory(InventoryManager):
for group_name in groups_data: for group_name in groups_data:
group = self.get_group(group_name) group = self.get_group(group_name)
if group is None: if group is None:
group = Group(group_name) self.add_group(group_name)
self.add_group(group) group = self.get_group(group_name)
group.add_host(host) group.add_host(host)
else: else:
ungrouped.add_host(host) ungrouped.add_host(host)
group_all.add_host(host) group_all.add_host(host)
def get_matched_hosts(self, pattern):
return self.get_hosts(pattern)
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
import os import os
from collections import namedtuple from collections import namedtuple
...@@ -11,7 +10,6 @@ from ansible.executor.playbook_executor import PlaybookExecutor ...@@ -11,7 +10,6 @@ from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.playbook.play import Play from ansible.playbook.play import Play
import ansible.constants as C import ansible.constants as C
from .inventory import JMSInventory
from .callback import AdHocResultCallback, PlaybookResultCallBack, \ from .callback import AdHocResultCallback, PlaybookResultCallBack, \
CommandResultCallback CommandResultCallback
from common.utils import get_logger from common.utils import get_logger
...@@ -71,36 +69,19 @@ class PlayBookRunner: ...@@ -71,36 +69,19 @@ class PlayBookRunner:
# Default results callback # Default results callback
results_callback_class = PlaybookResultCallBack results_callback_class = PlaybookResultCallBack
inventory_class = JMSInventory
loader_class = DataLoader loader_class = DataLoader
variable_manager_class = VariableManager variable_manager_class = VariableManager
options = get_default_options() 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 options: Ansible options like ansible.cfg
:param hosts: [ :param inventory: Ansible inventory
{
"hostname": "",
"ip": "",
"port": "",
"username": "",
"password": "",
"private_key": "",
"become": {
"method": "",
"user": "",
"pass": "",
},
"groups": [],
"vars": {},
},
]
""" """
if options: if options:
self.options = options self.options = options
C.RETRY_FILES_ENABLED = False C.RETRY_FILES_ENABLED = False
self.inventory = self.inventory_class(hosts) self.inventory = inventory
self.loader = self.loader_class() self.loader = self.loader_class()
self.results_callback = self.results_callback_class() self.results_callback = self.results_callback_class()
self.playbook_path = options.playbook_path self.playbook_path = options.playbook_path
...@@ -141,20 +122,19 @@ class AdHocRunner: ...@@ -141,20 +122,19 @@ class AdHocRunner:
ADHoc Runner接口 ADHoc Runner接口
""" """
results_callback_class = AdHocResultCallback results_callback_class = AdHocResultCallback
inventory_class = JMSInventory
loader_class = DataLoader loader_class = DataLoader
variable_manager_class = VariableManager variable_manager_class = VariableManager
options = get_default_options() options = get_default_options()
default_options = get_default_options() default_options = get_default_options()
def __init__(self, hosts, options=None): def __init__(self, inventory, options=None):
if options: if options:
self.options = options self.options = options
self.inventory = inventory
self.pattern = ''
self.loader = DataLoader() self.loader = DataLoader()
self.inventory = self.inventory_class(hosts) self.variable_manager = VariableManager(
self.variable_manager = VariableManager(loader=self.loader, inventory=self.inventory) loader=self.loader, inventory=self.inventory
)
@staticmethod @staticmethod
def check_module_args(module_name, module_args=''): def check_module_args(module_name, module_args=''):
...@@ -163,14 +143,22 @@ class AdHocRunner: ...@@ -163,14 +143,22 @@ class AdHocRunner:
raise AnsibleError(err) raise AnsibleError(err)
def check_pattern(self, pattern): def check_pattern(self, pattern):
if not pattern:
raise AnsibleError("Pattern `{}` is not valid!".format(pattern))
if not self.inventory.list_hosts("all"): if not self.inventory.list_hosts("all"):
raise AnsibleError("Inventory is empty.") raise AnsibleError("Inventory is empty.")
if not self.inventory.list_hosts(pattern): if not self.inventory.list_hosts(pattern):
raise AnsibleError( raise AnsibleError(
"pattern: %s dose not match any hosts." % pattern "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): def set_option(self, k, v):
kwargs = {k: v} kwargs = {k: v}
self.options = self.options._replace(**kwargs) self.options = self.options._replace(**kwargs)
...@@ -182,17 +170,15 @@ class AdHocRunner: ...@@ -182,17 +170,15 @@ class AdHocRunner:
:param play_name: The play name :param play_name: The play name
:return: :return:
""" """
self.check_pattern(pattern)
results_callback = self.results_callback_class() results_callback = self.results_callback_class()
clean_tasks = [] cleaned_tasks = self.clean_tasks(tasks)
for task in tasks:
self.check_module_args(task['action']['module'], task['action'].get('args'))
clean_tasks.append(task)
play_source = dict( play_source = dict(
name=play_name, name=play_name,
hosts=pattern, hosts=pattern,
gather_facts=gather_facts, gather_facts=gather_facts,
tasks=clean_tasks tasks=cleaned_tasks
) )
play = Play().load( play = Play().load(
...@@ -209,6 +195,9 @@ class AdHocRunner: ...@@ -209,6 +195,9 @@ class AdHocRunner:
stdout_callback=results_callback, stdout_callback=results_callback,
passwords=self.options.passwords, passwords=self.options.passwords,
) )
logger.debug("Get inventory matched hosts: {}".format(
self.inventory.get_matched_hosts(pattern)
))
try: try:
tqm.run(play) tqm.run(play)
......
...@@ -6,7 +6,7 @@ import unittest ...@@ -6,7 +6,7 @@ import unittest
sys.path.insert(0, '../..') sys.path.insert(0, '../..')
from ops.ansible.inventory import JMSInventory from ops.ansible.inventory import BaseInventory
class TestJMSInventory(unittest.TestCase): class TestJMSInventory(unittest.TestCase):
...@@ -41,7 +41,7 @@ class TestJMSInventory(unittest.TestCase): ...@@ -41,7 +41,7 @@ class TestJMSInventory(unittest.TestCase):
"vars": {"love": "yes"}, "vars": {"love": "yes"},
}] }]
self.inventory = JMSInventory(host_list=host_list) self.inventory = BaseInventory(host_list=host_list)
def test_hosts(self): def test_hosts(self):
print("#"*10 + "Hosts" + "#"*10) print("#"*10 + "Hosts" + "#"*10)
......
...@@ -7,6 +7,7 @@ import sys ...@@ -7,6 +7,7 @@ import sys
sys.path.insert(0, "../..") sys.path.insert(0, "../..")
from ops.ansible.runner import AdHocRunner, CommandRunner from ops.ansible.runner import AdHocRunner, CommandRunner
from ops.ansible.inventory import BaseInventory
class TestAdHocRunner(unittest.TestCase): class TestAdHocRunner(unittest.TestCase):
...@@ -20,7 +21,8 @@ class TestAdHocRunner(unittest.TestCase): ...@@ -20,7 +21,8 @@ class TestAdHocRunner(unittest.TestCase):
"password": "redhat", "password": "redhat",
}, },
] ]
self.runner = AdHocRunner(hosts=host_data) inventory = BaseInventory(host_data)
self.runner = AdHocRunner(inventory)
def test_run(self): def test_run(self):
tasks = [ tasks = [
...@@ -43,7 +45,8 @@ class TestCommandRunner(unittest.TestCase): ...@@ -43,7 +45,8 @@ class TestCommandRunner(unittest.TestCase):
"password": "redhat", "password": "redhat",
}, },
] ]
self.runner = CommandRunner(hosts=host_data) inventory = BaseInventory(host_data)
self.runner = CommandRunner(inventory)
def test_execute(self): def test_execute(self):
res = self.runner.execute('ls', 'all') res = self.runner.execute('ls', 'all')
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from django.shortcuts import get_object_or_404
from rest_framework import viewsets from rest_framework import viewsets
from .hands import IsSuperUser from .hands import IsSuperUser
from .models import AdHoc from .models import Task, AdHoc, AdHocRunHistory
from .serializers import TaskSerializer from .serializers import TaskSerializer, AdHocSerializer, AdHocRunHistorySerializer
class TaskViewSet(viewsets.ModelViewSet): class TaskViewSet(viewsets.ModelViewSet):
queryset = AdHoc.objects.all() queryset = Task.objects.all()
serializer_class = TaskSerializer serializer_class = TaskSerializer
permission_classes = (IsSuperUser,) 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 ...@@ -6,20 +6,25 @@ import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import signer from common.utils import signer
__all__ = ["AdHoc", "AdHocRunHistory"] __all__ = ["Task", "AdHoc", "AdHocRunHistory"]
logger = logging.getLogger(__name__) 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) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
is_deleted = models.BooleanField(default=False) is_deleted = models.BooleanField(default=False)
created_by = models.CharField(max_length=128, blank=True, default='') 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 @property
def short_id(self): def short_id(self):
...@@ -28,24 +33,48 @@ class AdHoc(models.Model): ...@@ -28,24 +33,48 @@ class AdHoc(models.Model):
def __str__(self): def __str__(self):
return self.name 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): class Meta:
BECOME_METHOD_CHOICES = ( db_table = 'ops_task'
('sudo', 'sudo'),
('su', 'su'),
) class AdHoc(models.Model):
version = models.UUIDField(default=uuid.uuid4, primary_key=True) """
subject = models.ForeignKey(AdHoc, on_delete=models.CASCADE) task: A task reference
_tasks = models.TextField(verbose_name=_('Tasks')) # [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ] _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'] _hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2']
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin')) run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
run_as = models.CharField(max_length=128, verbose_name=_("Run as")) run_as = models.CharField(max_length=128, default='', verbose_name=_("Run as"))
become = models.BooleanField(default=False, verbose_name=_("Become")) _become = models.CharField(max_length=1024, default='', verbose_name=_("Become"))
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) created_by = models.CharField(max_length=64, default='', verbose_name=_('Create by'))
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'))
date_created = models.DateTimeField(auto_now_add=True) date_created = models.DateTimeField(auto_now_add=True)
@property @property
...@@ -54,7 +83,10 @@ class AdHocData(models.Model): ...@@ -54,7 +83,10 @@ class AdHocData(models.Model):
@tasks.setter @tasks.setter
def tasks(self, item): 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 @property
def hosts(self): def hosts(self):
...@@ -65,42 +97,83 @@ class AdHocData(models.Model): ...@@ -65,42 +97,83 @@ class AdHocData(models.Model):
self._hosts = json.dumps(item) self._hosts = json.dumps(item)
@property @property
def become_pass(self): def become(self):
return signer.unsign(self._become_pass) 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 @options.setter
def become_pass(self, password): def options(self, item):
self._become_pass = signer.sign(password) self._options = json.dumps(item)
@property @property
def short_version(self): def short_id(self):
return str(self.version).split('-')[-1] return str(self.id).split('-')[-1]
def run(self): def get_latest_history(self):
pass return self.history.all().order_by('date_start').last()
def __str__(self): def __str__(self):
return "{} of {}".format(self.subject.name, self.short_version) return "{} of {}".format(self.task.name, self.short_id)
class Meta: class Meta:
db_table = "ops_adhoc_data" db_table = "ops_adhoc"
class AdHocRunHistory(models.Model): 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_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start time'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time')) date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time'))
timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True) timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
is_finished = models.BooleanField(default=False, verbose_name=_('Is finished')) is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
is_success = models.BooleanField(default=False, verbose_name=_('Is success')) is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
result = models.TextField(blank=True, null=True, verbose_name=_('Playbook raw result')) _result = models.TextField(blank=True, null=True, verbose_name=_('Adhoc raw result'))
summary = models.TextField(blank=True, null=True, verbose_name=_('Playbook summary')) _summary = models.TextField(blank=True, null=True, verbose_name=_('Adhoc result summary'))
@property @property
def short_id(self): def short_id(self):
return str(self.id).split('-')[-1] 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): def __str__(self):
return self.short_id return self.short_id
......
...@@ -2,12 +2,43 @@ ...@@ -2,12 +2,43 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from .models import AdHoc from .models import Task, AdHoc, AdHocRunHistory
class TaskSerializer(serializers.ModelSerializer): class TaskSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AdHoc model = Task
fields = '__all__' 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 # coding: utf-8
from __future__ import absolute_import, unicode_literals
from celery import shared_task 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 @shared_task
def rerun_task(task_id): def run_add_hoc_and_record_async(adhoc, **options):
from .models import Playbook return run_adhoc(adhoc, **options)
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)
{% 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 @@ ...@@ -15,8 +15,14 @@
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="panel-options"> <div class="panel-options">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="active"> <li class="active">
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Task replay detail' %} </a> <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> </li>
</ul> </ul>
</div> </div>
...@@ -43,49 +49,47 @@ ...@@ -43,49 +49,47 @@
<table class="table"> <table class="table">
<tbody> <tbody>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td width="20%">{% trans 'UUID' %}:</td> <td width="20%">{% trans 'ID' %}:</td>
<td><b>{{ object.uuid }}</b></td> <td><b>{{ object.id }}</b></td>
</tr> </tr>
<tr> <tr>
<td width="20%">{% trans 'Name' %}:</td> <td width="20%">{% trans 'Name' %}:</td>
<td><b>{{ object.name }}</b></td> <td><b>{{ object.name }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Date start' %}:</td> <td>{% trans 'Date created' %}:</td>
<td><b>{{ object.date_start }}</b></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>
<tr> <tr>
<td>{% trans 'Date finished' %}:</td> <td>{% trans 'Last version' %}</td>
<td><b>{{ object.date_finished }}</b></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>
<tr> <tr>
<td>{% trans 'Time delta' %}:</td> <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>
<tr> <tr>
<td>{% trans 'Is finished' %}:</td> <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>
<tr> <tr>
<td>{% trans 'Is success ' %}:</td> <td>{% trans 'Is success ' %}:</td>
{% if object.is_finished %} <td><b>{{ object.get_latest_history.is_success|yesno:"Yes,No,Unkown" }}</b></td>
<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 %}
</tr> </tr>
<tr> <tr>
<td>{% trans 'assets' %}:</td> <td>{% trans 'Conents' %}:</td>
<td> <td>
<b> <b>
{% for asset in object.total_assets %} {% for task in object.get_latest_adhoc.tasks %}
{{ asset.hostname }} <br/> {{ task.name }} : {{ task.action.module }} <br/>
{% endfor %} {% endfor %}
</b> </b>
</td> </td>
...@@ -94,31 +98,6 @@ ...@@ -94,31 +98,6 @@
</table> </table>
</div> </div>
</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>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0"> <div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-danger"> <div class="panel panel-danger">
...@@ -154,7 +133,7 @@ ...@@ -154,7 +133,7 @@
<div class="panel-body"> <div class="panel-body">
<table class="table"> <table class="table">
<tbody> <tbody>
{% for host in results.success %} {% for host in object.get_latest_history.summary.contacted %}
{% if forloop.first %} {% if forloop.first %}
<tr class="no-borders-tr"> <tr class="no-borders-tr">
{% else %} {% 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 @@ ...@@ -22,7 +22,7 @@
</div> </div>
</div> </div>
<div class="input-group"> <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>
<div class="input-group"> <div class="input-group">
<div class="input-group-btn"> <div class="input-group-btn">
...@@ -37,10 +37,11 @@ ...@@ -37,10 +37,11 @@
{% block table_head %} {% block table_head %}
<th class="text-center"></th> <th class="text-center"></th>
<th class="text-center">{% trans 'Name' %}</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 'Success' %}</th>
<th class="text-center">{% trans 'Finished' %}</th> <th class="text-center">{% trans 'Date' %}</th>
<th class="text-center">{% trans 'Date start' %}</th>
<th class="text-center">{% trans 'Time' %}</th> <th class="text-center">{% trans 'Time' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
{% endblock %} {% endblock %}
...@@ -49,26 +50,23 @@ ...@@ -49,26 +50,23 @@
{% for object in task_list %} {% for object in task_list %}
<tr class="gradeX"> <tr class="gradeX">
<td class="text-center"><input type="checkbox" class="cbx-term"> </td> <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"><a href="{% url 'ops:task-detail' pk=object.id %}">{{ object.name }}</a></td>
<td class="text-center">{{ object.total_assets|length }}</td>
<td class="text-center"> <td class="text-center">
{% if object.is_success %} <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}}
<i class="fa fa-check text-navy"></i>
{% else %}
<i class="fa fa-times text-danger"></i>
{% endif %}
</td> </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"> <td class="text-center">
{% if object.is_finished %} {% if object.get_latest_history.is_success %}
<i class="fa fa-check text-navy"></i> <i class="fa fa-check text-navy"></i>
{% else %} {% else %}
<i class="fa fa-times text-danger"></i> <i class="fa fa-times text-danger"></i>
{% endif %} {% endif %}
</td> </td>
<td class="text-center">{{ object.date_start }}</td> <td class="text-center">{{ object.get_latest_history.date_start }}</td>
<td class="text-center">{{ object.timedelta }} s</td> <td class="text-center">{{ object.get_latest_history.timedelta|floatformat }} s</td>
<td class="text-center"> <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> <a data-uid="{{ object.uuid }}" class="btn btn-xs btn-danger btn-del">{% trans "Delete" %}</a>
</td> </td>
</tr> </tr>
......
...@@ -5,17 +5,16 @@ import sys ...@@ -5,17 +5,16 @@ import sys
import os import os
from django.test import TestCase from django.test import TestCase
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings") from ops.models import Task, AdHoc
from ops.models import AdHoc, AdHocData from ops.utils import run_adhoc_object
from ops.utils import run_adhoc
class TestRunAdHoc(TestCase): class TestRunAdHoc(TestCase):
def setUp(self): def setUp(self):
adhoc = AdHoc(name="Test run adhoc") adhoc = Task(name="Test run adhoc")
adhoc.save() 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 = [ self.data.tasks = [
{'name': 'run ls', 'action': {'module': 'shell', 'args': 'ls'}}, {'name': 'run ls', 'action': {'module': 'shell', 'args': 'ls'}},
{'name': 'echo ', 'action': {'module': 'shell', 'args': 'echo 123'}}, {'name': 'echo ', 'action': {'module': 'shell', 'args': 'echo 123'}},
......
...@@ -7,6 +7,8 @@ from .. import api ...@@ -7,6 +7,8 @@ from .. import api
router = DefaultRouter() router = DefaultRouter()
router.register(r'v1/tasks', api.TaskViewSet, 'task') 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 = [] urlpatterns = []
......
...@@ -11,5 +11,7 @@ urlpatterns = [ ...@@ -11,5 +11,7 @@ urlpatterns = [
# TResource Task url # TResource Task url
url(r'^task/$', views.TaskListView.as_view(), name='task-list'), 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\-]+)/$', 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'), 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 ...@@ -4,20 +4,16 @@ import re
import time import time
from django.utils import timezone from django.utils import timezone
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none, get_short_uuid_str
from .ansible import AdHocRunner from .ansible import AdHocRunner, CommandResultCallback
from .inventory import JMSInventory
from .ansible.exceptions import AnsibleError from .ansible.exceptions import AnsibleError
from .models import AdHocRunHistory from .models import AdHocRunHistory, Task, AdHoc
from assets.utils import get_assets_by_hostname_list
logger = get_logger(__file__) logger = get_logger(__file__)
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}') UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
def run_AdHoc():
pass
def is_uuid(s): def is_uuid(s):
if UUID_PATTERN.match(s): if UUID_PATTERN.match(s):
return True return True
...@@ -25,97 +21,133 @@ def is_uuid(s): ...@@ -25,97 +21,133 @@ def is_uuid(s):
return False return False
def asset_to_dict(asset): def record_adhoc(func):
return asset.to_json() def _deco(adhoc, **options):
record = AdHocRunHistory(adhoc=adhoc)
time_start = time.time()
def asset_to_dict_with_credential(asset): try:
return asset._to_secret_json() result = func(adhoc, **options)
record.is_finished = True
if result.results_summary.get('dark'):
def system_user_to_dict_with_credential(system_user): record.is_success = False
return system_user._to_secret_json() else:
record.is_success = True
record.result = result.results_raw
def get_hosts_with_admin(hostname_list): record.summary = result.results_summary
assets = get_assets_by_hostname_list(hostname_list) return result
return [asset._to_secret_json for asset in assets] finally:
record.date_finished = timezone.now()
record.timedelta = time.time() - time_start
def get_hosts(hostname_list): record.save()
assets = get_assets_by_hostname_list(hostname_list) return _deco
return [asset.to_json for asset in assets]
def get_adhoc_inventory(adhoc):
def get_run_user(name): if adhoc.become:
from assets.models import SystemUser become_info = {
system_user = get_object_or_none(SystemUser, name=name) 'become': {
if system_user is None: adhoc.become
return {} }
}
else: 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: def get_inventory(hostname_list, run_as_admin=False, run_as=None, become_info=None):
host.update(system_user_dct) return JMSInventory(
return hosts_dict hostname_list, run_as_admin=run_as_admin,
run_as=run_as, become_info=become_info
)
def hosts_add_become(hosts, adhoc_data): def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info=None):
if adhoc_data.become: inventory = get_inventory(
become_data = { hostname_list, run_as_admin=run_as_admin,
"become": { run_as=run_as, become_info=become_info
"method": adhoc_data.become_method, )
"user": adhoc_data.become_user, runner = AdHocRunner(inventory)
"pass": adhoc_data.become_pass, return runner
}
}
for host in hosts:
host.update(become_data)
return hosts
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 ... :param options: ansible support option, like forks ...
:return: :return:
""" """
name = adhoc_data.subject.name name = adhoc.task.name
hostname_list = adhoc_data.hosts inventory = get_adhoc_inventory(adhoc)
if adhoc_data.run_as_admin: runner = AdHocRunner(inventory)
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)
for k, v in options: for k, v in options:
runner.set_option(k, v) runner.set_option(k, v)
record = AdHocRunHistory(adhoc=adhoc_data)
time_start = time.time()
try: try:
result = runner.run(adhoc_data.tasks, adhoc_data.pattern, name) result = runner.run(adhoc.tasks, adhoc.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
return result return result
except AnsibleError as e: except AnsibleError as e:
logger.error("Failed run adhoc {}, {}".format(name, e)) logger.error("Failed run adhoc {}, {}".format(name, e))
raise 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 ...@@ -9,40 +9,40 @@ from django.views.generic import ListView, DetailView, View
from django.utils import timezone from django.utils import timezone
from django.shortcuts import redirect, reverse from django.shortcuts import redirect, reverse
from .models import AdHoc, AdHocData, AdHocRunHistory from .models import Task, AdHoc, AdHocRunHistory
from ops.tasks import rerun_task from ops.tasks import rerun_task
class TaskListView(ListView): class TaskListView(ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
model = AdHoc model = Task
ordering = ('-date_start',) ordering = ('-date_created',)
context_object_name = 'task_list' context_object_name = 'task_list'
template_name = 'ops/task_list.html' template_name = 'ops/task_list.html'
date_format = '%m/%d/%Y' date_format = '%m/%d/%Y'
keyword = date_from_s = date_to_s = '' keyword = date_from_s = date_to_s = ''
def get_queryset(self): def get_queryset(self):
date_now = timezone.localtime(timezone.now()) date_to_default = timezone.now()
date_to_default = date_now.strftime(self.date_format) date_from_default = timezone.now() - timezone.timedelta(7)
date_from_default = (date_now - timezone.timedelta(7)) \ date_from_default_s = date_from_default.strftime(self.date_format)
.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.keyword = self.request.GET.get('keyword', '')
self.date_from_s = self.request.GET.get('date_from', date_from_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) self.date_to_s = self.request.GET.get('date_to', date_to_default_s)
if self.date_from_s: if self.date_from_s:
date_from = datetime.strptime(self.date_from_s, self.date_format) date_from = datetime.strptime(self.date_from_s, self.date_format)
date_from = date_from.replace(tzinfo=timezone.get_current_timezone()) 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: if self.date_to_s:
date_to = timezone.datetime.strptime( date_to = timezone.datetime.strptime(
self.date_to_s + ' 23:59:59', '%m/%d/%Y %H:%M:%S') self.date_to_s + ' 23:59:59', '%m/%d/%Y %H:%M:%S')
date_to = date_to.replace(tzinfo=timezone.get_current_timezone()) 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: if self.keyword:
self.queryset = self.queryset.filter( self.queryset = self.queryset.filter(
...@@ -63,17 +63,42 @@ class TaskListView(ListView): ...@@ -63,17 +63,42 @@ class TaskListView(ListView):
class TaskDetailView(DetailView): class TaskDetailView(DetailView):
model = AdHocRunHistory model = Task
template_name = 'ops/task_detail.html' template_name = 'ops/task_detail.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': 'Ops', 'app': 'Ops',
'action': 'Playbook record detail', 'action': 'Task detail',
'results': json.loads(self.object.summary or '{}'),
} }
kwargs.update(context) 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): class TaskRunView(View):
......
...@@ -3,46 +3,11 @@ from __future__ import absolute_import, unicode_literals ...@@ -3,46 +3,11 @@ from __future__ import absolute_import, unicode_literals
from celery import shared_task from celery import shared_task
from common.utils import get_logger, encrypt_password from common.utils import get_logger, encrypt_password
from ops.utils import run_AdHoc
logger = get_logger(__file__) logger = get_logger(__file__)
@shared_task(bind=True) @shared_task(bind=True)
def push_users(self, assets, users): def push_users(self, assets, users):
""" pass
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
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
<i class="fa fa-coffee"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span> <i class="fa fa-coffee"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
</a> </a>
<ul class="nav nav-second-level"> <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> </ul>
</li> </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