diff --git a/apps/assets/api.py b/apps/assets/api.py
index da363dee4..3cc9e0773 100644
--- a/apps/assets/api.py
+++ b/apps/assets/api.py
@@ -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:
diff --git a/apps/assets/models.py b/apps/assets/models.py
deleted file mode 100644
index ec51c5a2b..000000000
--- a/apps/assets/models.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# -*- coding: utf-8 -*-
-#
diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py
index 939a1307b..38ed90721 100644
--- a/apps/assets/models/__init__.py
+++ b/apps/assets/models/__init__.py
@@ -1,8 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
-print("Import assets model")
-
from .user import AdminUser, SystemUser
from .cluster import *
from .group import *
diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py
index 00c5b8582..e5656ca97 100644
--- a/apps/assets/models/asset.py
+++ b/apps/assets/models/asset.py
@@ -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
diff --git a/apps/assets/models/group.py b/apps/assets/models/group.py
index 49f25e51f..31a1cd2d5 100644
--- a/apps/assets/models/group.py
+++ b/apps/assets/models/group.py
@@ -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'))
diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py
index 5b2388cf0..47bc41ce5 100644
--- a/apps/assets/models/user.py
+++ b/apps/assets/models/user.py
@@ -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,
diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py
index 5a41cc386..0a51adfb7 100644
--- a/apps/assets/serializers.py
+++ b/apps/assets/serializers.py
@@ -1,11 +1,11 @@
# -*- 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):
diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py
index 2c5267281..5ac899abd 100644
--- a/apps/assets/tasks.py
+++ b/apps/assets/tasks.py
@@ -1,23 +1,45 @@
# ~*~ 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
diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html
new file mode 100644
index 000000000..62244b5f0
--- /dev/null
+++ b/apps/assets/templates/assets/admin_user_assets.html
@@ -0,0 +1,387 @@
+{% extends 'base.html' %}
+{% load static %}
+{% load i18n %}
+
+{% block custom_head_css_js %}
+
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+
{% trans 'Asset list of ' %} {{ admin_user.name }} {{ total_amount }} {{ unreachable_amount }}
+
+
+
+
+
+
+ {% trans 'Hostname' %} |
+ {% trans 'IP' %} |
+ {% trans 'Port' %} |
+ {% trans 'Alive' %} |
+
+
+
+ {% for asset in page_obj %}
+
+ {{ asset.hostname }} |
+ {{ asset.ip }} |
+ {{ asset.port }} |
+ {% if asset.is_connective == '1' %}
+
+
+ |
+ {% else %}
+
+
+ |
+ {% endif %}
+
+ {% endfor %}
+
+
+
+ {% include '_pagination.html' %}
+
+
+
+
+
+
+
+ {% trans 'Quick update' %}
+
+
+
+
+
+ {% trans 'Retest connectivity' %}: |
+
+
+
+
+ |
+
+
+
+
+
+
+
+ {% trans 'Replace asset admin user with this' %}
+
+
+
+
+
+
+ {% trans 'Replace asset group admin user with this' %}
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+{% block custom_foot_js %}
+
+{% endblock %}
diff --git a/apps/assets/templates/assets/admin_user_detail.html b/apps/assets/templates/assets/admin_user_detail.html
index 7e5e5970d..f11834880 100644
--- a/apps/assets/templates/assets/admin_user_detail.html
+++ b/apps/assets/templates/assets/admin_user_detail.html
@@ -15,7 +15,10 @@
-
-
-
-
{% trans 'Asset list of ' %} {{ admin_user.name }} {{ paginator.count }}
-
-
-
-
-
-
- {% trans 'Hostname' %} |
- {% trans 'IP' %} |
- {% trans 'Port' %} |
- {% trans 'Alive' %} |
- {% trans 'Action' %} |
-
-
-
-{# {% for asset in page_obj %}#}
-{# #}
-{# {{ asset.hostname }} | #}
-{# {{ asset.ip }} | #}
-{# {{ asset.port }} | #}
-{# Alive | #}
-{#
#}
-{# {% endfor %}#}
-
-
-{#
#}
-{# {% include '_pagination.html' %}#}
-{#
#}
-
-
@@ -128,24 +86,6 @@
- {% trans 'Get install script' %}: |
-
-
-
-
- |
-
-
-
- {% trans 'Retest asset connectivity' %}: |
-
-
-
-
- |
-
-
-
{% trans 'Reset private key' %}: |
@@ -153,62 +93,14 @@
|
-
-
-
-
-
-
-
- {% trans 'Replace asset admin user with this' %}
-
-
-
-
-
-
- {% trans 'Replace asset admin user with this admin user' %}
-
-
-
-
-
+
+ {% trans 'Reset password' %}: |
+
+
+
+
+ |
+
@@ -361,7 +253,7 @@ $(document).ready(function () {
});
assets.unique();
var data = [];
- var admin_user_id = {{ admin_user.id }};
+ var admin_user_id = "{{ admin_user.id }}";
var the_url = '{% url "api-assets:asset-list" %}';
for (var i=0; i
{% trans 'Name' %}
{% trans 'Username' %} |
{% trans 'Asset num' %} |
+ {% trans 'Unreachable' %} |
{% trans 'Comment' %} |
{% trans 'Action' %} |
@@ -34,11 +35,7 @@ $(document).ready(function(){
var detail_btn = '' + cellData + '';
$(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('' + innerHtml + '');
- }},
- {targets: 5, createdCell: function (td, cellData, rowData) {
+ {targets: 6, createdCell: function (td, cellData, rowData) {
{# var script_btn = '{% trans "Script" %}'.replace('99991937', cellData);#}
var update_btn = '{% trans "Update" %}'.replace('99991937', cellData);
var del_btn = '{% trans "Delete" %}'.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);
})
diff --git a/apps/assets/templates/assets/system_user_asset.html b/apps/assets/templates/assets/system_user_asset.html
index 9795b4667..fda20d19f 100644
--- a/apps/assets/templates/assets/system_user_asset.html
+++ b/apps/assets/templates/assets/system_user_asset.html
@@ -58,18 +58,16 @@
{{ asset.ip }} |
{{ asset.port }} |
+
|
-
+
|
{% endfor %}
-{# #}
-{# {% include '_pagination.html' %}#}
-{#
#}
diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html
index 5e8804a86..9192daefb 100644
--- a/apps/assets/templates/assets/system_user_list.html
+++ b/apps/assets/templates/assets/system_user_list.html
@@ -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()
};
diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py
index 0353b5b36..b83a00120 100644
--- a/apps/assets/urls/views_urls.py
+++ b/apps/assets/urls/views_urls.py
@@ -41,6 +41,7 @@ urlpatterns = [
url(r'^admin-user/(?P[0-9a-zA-Z\-]+)/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
url(r'^admin-user/(?P[0-9a-zA-Z\-]+)/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
url(r'^admin-user/(?P[0-9a-zA-Z\-]+)/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
+ url(r'^admin-user/(?P[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'),
diff --git a/apps/assets/utils.py b/apps/assets/utils.py
index 6e9493f3f..f7efb50ff 100644
--- a/apps/assets/utils.py
+++ b/apps/assets/utils.py
@@ -1,20 +1,7 @@
# ~*~ 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
diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py
index b45437d3a..0cc4f66f2 100644
--- a/apps/assets/views/admin_user.py
+++ b/apps/assets/views/admin_user.py
@@ -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'
diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py
index 801bc78f4..0d3ccfe8b 100644
--- a/apps/assets/views/system_user.py
+++ b/apps/assets/views/system_user.py
@@ -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)
diff --git a/apps/common/celery.py b/apps/common/celery.py
index 18b5f755d..98270a12c 100644
--- a/apps/common/celery.py
+++ b/apps/common/celery.py
@@ -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': (),
},
diff --git a/apps/common/utils.py b/apps/common/utils.py
index fb9bc6937..afbfa06cb 100644
--- a/apps/common/utils.py
+++ b/apps/common/utils.py
@@ -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()
diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py
index 795223d84..596563981 100644
--- a/apps/jumpserver/settings.py
+++ b/apps/jumpserver/settings.py
@@ -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 = [
diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py
index ac46d82cf..cc4bc6215 100644
--- a/apps/ops/ansible/callback.py
+++ b/apps/ops/ansible/callback.py
@@ -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)
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index fb58b4ffd..d3d342368 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -1,12 +1,16 @@
# ~*~ 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)
+
+
diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py
index 6fa765a67..37a818fe0 100644
--- a/apps/ops/ansible/runner.py
+++ b/apps/ops/ansible/runner.py
@@ -1,5 +1,4 @@
# ~*~ 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)
diff --git a/apps/ops/ansible/test_inventory.bak b/apps/ops/ansible/test_inventory.py
similarity index 93%
rename from apps/ops/ansible/test_inventory.bak
rename to apps/ops/ansible/test_inventory.py
index d4ff43f24..00c7fa459 100644
--- a/apps/ops/ansible/test_inventory.bak
+++ b/apps/ops/ansible/test_inventory.py
@@ -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)
diff --git a/apps/ops/ansible/test_runner.bak b/apps/ops/ansible/test_runner.py
similarity index 84%
rename from apps/ops/ansible/test_runner.bak
rename to apps/ops/ansible/test_runner.py
index ec1da5fa5..07c9051f3 100644
--- a/apps/ops/ansible/test_runner.bak
+++ b/apps/ops/ansible/test_runner.py
@@ -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')
diff --git a/apps/ops/api.py b/apps/ops/api.py
index 35eadd2ac..7e16974d7 100644
--- a/apps/ops/api.py
+++ b/apps/ops/api.py
@@ -1,15 +1,42 @@
# ~*~ 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
diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py
new file mode 100644
index 000000000..ce3dd96de
--- /dev/null
+++ b/apps/ops/inventory.py
@@ -0,0 +1,46 @@
+# -*- 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()
diff --git a/apps/ops/models.py b/apps/ops/models.py
index a91b0cfa9..e242a0867 100644
--- a/apps/ops/models.py
+++ b/apps/ops/models.py
@@ -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()
-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..': ''}, ]
+ 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 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_pass.setter
- def become_pass(self, password):
- self._become_pass = signer.sign(password)
+ @become.setter
+ def become(self, item):
+ """
+ :param item: {
+ method: "sudo",
+ user: "user",
+ pass: "pass",
+ }
+ :return:
+ """
+ self._become = signer.sign(json.dumps(item))
@property
- def short_version(self):
- return str(self.version).split('-')[-1]
+ def options(self):
+ if self._options:
+ return json.loads(self._options)
+ else:
+ return {}
- def run(self):
- pass
+ @options.setter
+ def options(self, item):
+ self._options = json.dumps(item)
+
+ @property
+ def short_id(self):
+ return str(self.id).split('-')[-1]
+
+ 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
diff --git a/apps/ops/serializers.py b/apps/ops/serializers.py
index 35727be20..2ffb21c06 100644
--- a/apps/ops/serializers.py
+++ b/apps/ops/serializers.py
@@ -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
diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py
index 669b3741e..c5298377d 100644
--- a/apps/ops/tasks.py
+++ b/apps/ops/tasks.py
@@ -1,22 +1,13 @@
# 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)
diff --git a/apps/ops/templates/ops/task_adhoc.html b/apps/ops/templates/ops/task_adhoc.html
new file mode 100644
index 000000000..21232ad82
--- /dev/null
+++ b/apps/ops/templates/ops/task_adhoc.html
@@ -0,0 +1,117 @@
+{% extends 'base.html' %}
+{% load static %}
+{% load i18n %}
+
+{% block custom_head_css_js %}
+
+
+
+
+{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+
+
+
{% trans 'Versions of ' %} {{ object.name }}
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block custom_foot_js %}
+
+{% endblock %}
diff --git a/apps/ops/templates/ops/task_detail.html b/apps/ops/templates/ops/task_detail.html
index ad3a01624..bc98f9ff1 100644
--- a/apps/ops/templates/ops/task_detail.html
+++ b/apps/ops/templates/ops/task_detail.html
@@ -15,8 +15,14 @@
@@ -43,49 +49,47 @@
- {% trans 'UUID' %}: |
- {{ object.uuid }} |
+ {% trans 'ID' %}: |
+ {{ object.id }} |
{% trans 'Name' %}: |
{{ object.name }} |
- {% trans 'Date start' %}: |
- {{ object.date_start }} |
+ {% trans 'Date created' %}: |
+ {{ object.date_created }} |
- {% trans 'Date finished' %}: |
- {{ object.date_finished }} |
+ {% trans 'Total versions' %} |
+ {{ object.adhoc.all |length }} |
+
+
+ {% trans 'Last version' %} |
+ {{ object.get_latest_adhoc.short_id }} |
+
+
+ {% trans 'Latest run' %}: |
+ {{ object.get_latest_history.date_start }} |
{% trans 'Time delta' %}: |
- {{ object.timedelta}} s |
+ {{ object.get_latest_history.timedelta|floatformat}} s |
{% trans 'Is finished' %}: |
- {{ object.is_finished|yesno:"Yes,No,Unkown" }} |
+ {{ object.get_latest_history.is_finished|yesno:"Yes,No,Unkown" }} |
{% trans 'Is success ' %}: |
- {% if object.is_finished %}
- {{ object.is_success|yesno:"Yes,No,Unkown" }} |
- {% else %}
-
-
-
- 40% Complete (success)
-
-
- |
- {% endif %}
+ {{ object.get_latest_history.is_success|yesno:"Yes,No,Unkown" }} |
- {% trans 'assets' %}: |
+ {% trans 'Conents' %}: |
- {% for asset in object.total_assets %}
- {{ asset.hostname }}
+ {% for task in object.get_latest_adhoc.tasks %}
+ {{ task.name }} : {{ task.action.module }}
{% endfor %}
|
@@ -94,31 +98,6 @@
-
-
-
-
-
-{{ object.result }}
-
-
-
-
@@ -154,7 +133,7 @@
- {% for host in results.success %}
+ {% for host in object.get_latest_history.summary.contacted %}
{% if forloop.first %}
{% else %}
diff --git a/apps/ops/templates/ops/task_history.html b/apps/ops/templates/ops/task_history.html
new file mode 100644
index 000000000..6fde3b742
--- /dev/null
+++ b/apps/ops/templates/ops/task_history.html
@@ -0,0 +1,123 @@
+{% extends 'base.html' %}
+{% load static %}
+{% load i18n %}
+
+{% block custom_head_css_js %}
+
+
+
+
+{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+
+
+
{% trans 'Versions of ' %} {{ object.name }}
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block custom_foot_js %}
+
+{% endblock %}
diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html
index dac614805..765525318 100644
--- a/apps/ops/templates/ops/task_list.html
+++ b/apps/ops/templates/ops/task_list.html
@@ -22,7 +22,7 @@
-
+