@@ -88,16 +95,19 @@
$('.password-auth').removeClass('hidden');
$('.public-key-auth').addClass('hidden');
$('#'+'{{ form.password.id_for_label }}').attr('required', 'required');
+ $('#'+'{{ form.password.id_for_label }}').removeAttr('disabled');
} else if ($(auth_method).val() == 'K') {
$('.password-auth').addClass('hidden');
$('.public-key-auth').removeClass('hidden');
+ $('#'+'{{ form.password.id_for_label }}').removeAttr('required');
+ $('#'+'{{ form.password.id_for_label }}').attr('disabled', 'disabled');
if ($(auto_generate_key).prop('checked')){
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden');
} else {
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').removeClass('hidden');
-{# $('#'+'{{ form.private_key_file.id_for_label }}').attr('required', 'required');#}
+ $('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group input').attr('required', 'required');
}
}
}
@@ -110,14 +120,8 @@
$(auto_generate_key).change(function () {
authMethodDisplay();
});
-
-
- if ($('#'+'{{ form.protocol.id_for_label }}').val() == 'telnet') {
- $('#'+'{{ form.auto_generate_key.id_for_label }}').closest('.form-group').remove();
- $('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').remove();
- $('#'+'{{ form.auto_push.id_for_label }}').closest('.form-group').remove();
- $('#'+'{{ form.auto_update.id_for_label }}').closest('.form-group').remove();
- }
})
+
+
{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/templates/assets/system_user_create.html b/apps/assets/templates/assets/system_user_create.html
new file mode 100644
index 000000000..7127de993
--- /dev/null
+++ b/apps/assets/templates/assets/system_user_create.html
@@ -0,0 +1,7 @@
+{% extends 'assets/_system_user.html' %}
+{% load i18n %}
+{% load static %}
+
+{% block auth %}
+ {{ block.super }}
+{% endblock %}
diff --git a/apps/assets/templates/assets/system_user_update.html b/apps/assets/templates/assets/system_user_update.html
new file mode 100644
index 000000000..1d63cfcd3
--- /dev/null
+++ b/apps/assets/templates/assets/system_user_update.html
@@ -0,0 +1,15 @@
+{% extends 'assets/_system_user.html' %}
+{% load i18n %}
+{% load static %}
+{% load bootstrap %}
+
+{% block auth %}
+
+ {{ form.password|bootstrap_horizontal }}
+
+
+
+ {{ form.private_key_file|bootstrap_horizontal }}
+
+
+{% endblock %}
diff --git a/apps/assets/utils.py b/apps/assets/utils.py
index e6269e3b1..4eeb3ed65 100644
--- a/apps/assets/utils.py
+++ b/apps/assets/utils.py
@@ -4,6 +4,7 @@ from rest_framework import serializers
from models import Tag
from django.views.generic.edit import CreateView
+
class CreateAssetTagsMiXin(CreateView):
def get_form_kwargs(self):
tags_list = self.request.POST.getlist('tags')
@@ -30,6 +31,7 @@ class CreateAssetTagsMiXin(CreateView):
})
return kwargs
+
class UpdateAssetTagsMiXin(CreateAssetTagsMiXin):
def get_form_kwargs(self):
kwargs = super(UpdateAssetTagsMiXin, self).get_form_kwargs()
diff --git a/apps/assets/views.py b/apps/assets/views.py
index df9a4b1e2..75abc50c7 100644
--- a/apps/assets/views.py
+++ b/apps/assets/views.py
@@ -9,6 +9,7 @@ from openpyxl import load_workbook
from django.utils.translation import ugettext as _
from django.conf import settings
from django.db.models import Q
+from django.db import transaction
from django.db import IntegrityError
from django.views.generic import TemplateView, ListView, View
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
@@ -521,9 +522,13 @@ class SystemUserListView(AdminUserRequiredMixin, TemplateView):
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = SystemUser
form_class = forms.SystemUserForm
- template_name = 'assets/system_user_create_update.html'
+ template_name = 'assets/system_user_create.html'
success_url = reverse_lazy('assets:system-user-list')
+ @transaction.atomic
+ def post(self, request, *args, **kwargs):
+ return super(SystemUserCreateView, self).post(request, *args, **kwargs)
+
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
@@ -549,7 +554,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView):
model = SystemUser
form_class = forms.SystemUserForm
- template_name = 'assets/system_user_create_update.html'
+ template_name = 'assets/system_user_update.html'
def get_context_data(self, **kwargs):
context = {
diff --git a/apps/common/utils.py b/apps/common/utils.py
index cccfe1aec..7c0c6c8d4 100644
--- a/apps/common/utils.py
+++ b/apps/common/utils.py
@@ -16,6 +16,7 @@ import calendar
import threading
import paramiko
+from passlib.hash import sha512_crypt
import sshpubkeys
from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \
BadSignature, SignatureExpired
@@ -322,4 +323,11 @@ def make_signature(access_key_secret, date=None):
return content_md5(data)
+def encrypt_password(password):
+ from passlib.hash import sha512_crypt
+ if password:
+ return sha512_crypt.using(rounds=5000).hash(password)
+ return None
+
+
signer = Signer()
\ No newline at end of file
diff --git a/apps/ops/models.py b/apps/ops/models.py
new file mode 100644
index 000000000..0bdc65ae6
--- /dev/null
+++ b/apps/ops/models.py
@@ -0,0 +1,31 @@
+# ~*~ coding: utf-8 ~*~
+from __future__ import unicode_literals, absolute_import
+
+import logging
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+__all__ = ["TaskRecord"]
+
+
+logger = logging.getLogger(__name__)
+
+
+class TaskRecord(models.Model):
+ uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True)
+ name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
+ date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start time'))
+ date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time'))
+ is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
+ is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
+ assets = models.TextField(blank=True, null=True, verbose_name=_('Assets'))
+ result = models.TextField(blank=True, null=True, verbose_name=_('Task result'))
+
+ def __unicode__(self):
+ return "%s" % self.uuid
+
+ @property
+ def total_assets(self):
+ return self.assets.split(',')
+
diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py
deleted file mode 100644
index dbd842bab..000000000
--- a/apps/ops/models/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from ansible import *
-from utils import *
-from task import *
-
diff --git a/apps/ops/models/ansible.py b/apps/ops/models/ansible.py
deleted file mode 100644
index 3cf059644..000000000
--- a/apps/ops/models/ansible.py
+++ /dev/null
@@ -1,291 +0,0 @@
-# ~*~ coding: utf-8 ~*~
-from __future__ import unicode_literals, absolute_import
-
-import logging
-import json
-
-from django.db import models
-from django.utils.translation import ugettext_lazy as _
-
-__all__ = ["TaskRecord", "AnsiblePlay", "AnsibleTask", "AnsibleHostResult"]
-
-
-logger = logging.getLogger(__name__)
-
-
-class TaskRecord(models.Model):
- uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True)
- name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
- start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start Time'))
- end = models.DateTimeField(blank=True, null=True, verbose_name=_('End Time'))
- exit_code = models.IntegerField(default=0, verbose_name=_('Exit Code'))
- completed = models.BooleanField(default=False, verbose_name=_('Is Completed'))
- hosts = models.TextField(blank=True, null=True, verbose_name=_('Hosts'))
-
- def __unicode__(self):
- return "%s" % self.uuid
-
- @property
- def total_hosts(self):
- return self.hosts.split(',')
-
- @classmethod
- def generate_fake(cls, count=20):
- from random import seed
- from uuid import uuid4
- import forgery_py
-
- seed()
- for i in range(count):
- tasker = cls(uuid=str(uuid4()),
- name=forgery_py.name.full_name(),
- )
- try:
- tasker.save()
- logger.debug('Generate fake tasker: %s' % tasker.name)
- except Exception as e:
- print('Error: %s, continue...' % e.message)
- continue
-
-
-class AnsiblePlay(models.Model):
- tasker = models.ForeignKey(TaskRecord, related_name='plays', blank=True, null=True)
- uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True)
- name = models.CharField(max_length=128, verbose_name=_('Name'))
-
- def __unicode__(self):
- return "%s<%s>" % (self.name, self.uuid)
-
- def to_dict(self):
- return {"uuid": self.uuid, "name": self.name}
-
- @classmethod
- def generate_fake(cls, count=20):
- from random import seed, choice
- from uuid import uuid4
- import forgery_py
-
- seed()
- for i in range(count):
- play = cls(uuid=str(uuid4()),
- name=forgery_py.name.full_name(),
- )
- try:
- play.tasker = choice(TaskRecord.objects.all())
- play.save()
- logger.debug('Generate fake play: %s' % play.name)
- except Exception as e:
- print('Error: %s, continue...' % e.message)
- continue
-
-
-class AnsibleTask(models.Model):
- play = models.ForeignKey(AnsiblePlay, related_name='tasks', blank=True, null=True)
- uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True)
- name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
-
- def __unicode__(self):
- return "%s<%s>" % (self.name, self.uuid)
-
- def to_dict(self):
- return {"uuid": self.uuid, "name": self.name}
-
- def failed(self):
- pass
-
- def success(self):
- pass
-
- @classmethod
- def generate_fake(cls, count=20):
- from random import seed, choice
- from uuid import uuid4
- import forgery_py
-
- seed()
- for i in range(count):
- task = cls(uuid=str(uuid4()),
- name=forgery_py.name.full_name(),
- )
- try:
- task.play = choice(AnsiblePlay.objects.all())
- task.save()
- logger.debug('Generate fake play: %s' % task.name)
- except Exception as e:
- print('Error: %s, continue...' % e.message)
- continue
-
-
-class AnsibleHostResult(models.Model):
- task = models.ForeignKey(AnsibleTask, related_name='host_results', blank=True, null=True)
- name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
- success = models.TextField(blank=True, verbose_name=_('Success'))
- skipped = models.TextField(blank=True, verbose_name=_('Skipped'))
- failed = models.TextField(blank=True, verbose_name=_('Failed'))
- unreachable = models.TextField(blank=True, verbose_name=_('Unreachable'))
- no_host = models.TextField(blank=True, verbose_name=_('NoHost'))
-
- def __unicode__(self):
- return "%s %s<%s>" % (self.name, str(self.is_success), self.task.uuid)
-
- @property
- def is_failed(self):
- if self.failed or self.unreachable or self.no_host:
- return True
- return False
-
- @property
- def is_success(self):
- return not self.is_failed
-
- @property
- def _success_data(self):
- if self.success:
- return json.loads(self.success)
- elif self.skipped:
- return json.loads(self.skipped)
-
- @property
- def _failed_data(self):
- if self.failed:
- return json.loads(self.failed)
- elif self.unreachable:
- return json.loads(self.unreachable)
- elif self.no_host:
- return {"msg": self.no_host}
-
- @property
- def failed_msg(self):
- return self._failed_data.get("msg")
-
- @staticmethod
- def __filter_disk(ansible_devices, exclude_devices):
- """
- 过滤磁盘设备,丢弃掉不需要的设备
-
- :param ansible_devices: 对应的facts字段
- :param exclude_devices:
一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃sr0子类的 ['sr']
- :return: 过滤获取的结果
- """
- for start_str in exclude_devices:
- for key in ansible_devices.keys():
- if key.startswith(start_str):
- ansible_devices.pop(key)
- return ansible_devices
-
- @staticmethod
- def __filter_interface(ansible_interfaces, exclude_interface):
- """
- 过滤网卡设备,丢弃掉不需要的网卡, 比如lo
-
- :param ansible_interface: 对应的facts字段
- :param exclude_interface: 一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃lo子类的 ['lo']
- :return: 过滤获取的结果
- """
- for interface in ansible_interfaces:
- for start_str in exclude_interface:
- if interface.startswith(start_str):
- i = ansible_interfaces.index(interface)
- ansible_interfaces.pop(i)
- return ansible_interfaces
-
- @staticmethod
- def __gather_interface(facts, interfaces):
- """
- 收集所有interface的具体信息
-
- :param facts: ansible faces
- :param interfaces: 需要收集的intreface列表
- :return: interface的详情
- """
- result = {}
- for key in interfaces:
- gather_key = "ansible_" + key
- if gather_key in facts.keys():
- result[key] = facts.get(gather_key)
- return result
-
- def __deal_setup(self):
- """
- 处理ansible setup模块收集到的数据,提取资产需要的部分
-
- :return: {"msg": , "data": }, 注意msg是异常信息, 有msg时 data为None
- """
- result = self._success_data
- module_name = result['invocation'].get('module_name') if result.get('invocation') else None
- if module_name is not None:
- if module_name != "setup":
- return {"msg": "the property only for ansible setup module result!, can't support other module", "data":None}
- else:
- data = {}
- facts =result.get('ansible_facts')
- interfaces = self.__filter_interface(facts.get('ansible_interfaces'), ['lo'])
-
- cpu_describe = "%s %s" % (facts.get('ansible_processor')[0], facts.get('ansible_processor')[1]) if len(facts.get('ansible_processor')) >= 2 else ""
-
- data['sn'] = facts.get('ansible_product_serial')
- data['env'] = facts.get('ansible_env')
- data['os'] = "%s %s(%s)" % (facts.get('ansible_distribution'),
- facts.get('ansible_distribution_version'),
- facts.get('ansible_distribution_release'))
- data['mem'] = facts.get('ansible_memtotal_mb')
- data['cpu'] = "%s %d核" % (cpu_describe, facts.get('ansible_processor_count'))
- data['disk'] = self.__filter_disk(facts.get('ansible_devices'), ['sr'])
- data['interface'] = self.__gather_interface(facts, interfaces)
- return {"msg": None, "data": data}
- else:
- return {"msg": "there result isn't ansible setup module result! can't process this data format", "data": None}
-
- @property
- def deal_setup(self):
- try:
- return self.__deal_setup()
- except Exception as e:
- return {"msg": "deal with setup data failed, %s" % e.message, "data": None}
-
- def __deal_ping(self):
- """
- 处理ansible ping模块收集到的数据
-
- :return: {"msg": , "data": {"success": }}, 注意msg是异常信息, 有msg时 data为None
- """
- result = self._success_data
- module_name = result['invocation'].get('module_name') if result.get('invocation') else None
- if module_name is not None:
- if module_name != "ping":
- return {"msg": "the property only for ansible setup module result!, can't support other module", "data":None}
- else:
- ping = True if result.get('ping') == "pong" else False
-
- return {"msg": None, "data": {"success": ping}}
- else:
- return {"msg": "there isn't module_name field! can't process this data format", "data": None}
-
- @property
- def deal_ping(self):
- try:
- return self.__deal_ping()
- except Exception as e:
- return {"msg": "deal with ping data failed, %s" % e.message, "data": None}
-
- @classmethod
- def generate_fake(cls, count=20):
- from random import seed, choice
- import forgery_py
-
- seed()
- for i in range(count):
- result = cls(name=forgery_py.name.full_name(),
- success=forgery_py.lorem_ipsum.sentence(),
- failed=forgery_py.lorem_ipsum.sentence(),
- skipped=forgery_py.lorem_ipsum.sentence(),
- unreachable=forgery_py.lorem_ipsum.sentence(),
- no_host=forgery_py.lorem_ipsum.sentence(),
- )
- try:
- result.task = choice(AnsibleTask.objects.all())
- result.save()
- logger.debug('Generate fake HostResult: %s' % result.name)
- except Exception as e:
- print('Error: %s, continue...' % e.message)
- continue
diff --git a/apps/ops/models/task.py b/apps/ops/models/task.py
deleted file mode 100644
index 9dd3c5b24..000000000
--- a/apps/ops/models/task.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# ~*~ coding: utf-8 ~*~
-from __future__ import unicode_literals, absolute_import
-
-import logging
-
-from uuid import uuid4
-from assets.models import Asset
-from ops.models import TaskRecord
-
-from django.db import models
-from django.utils.translation import ugettext_lazy as _
-
-__all__ = ["Task"]
-
-
-class Task(models.Model):
- """
- Ansible 的Task
- """
- name = models.CharField(max_length=128, verbose_name=_('Task name'))
- module_name = models.CharField(max_length=128, verbose_name=_('Task module'))
- module_args = models.CharField(max_length=512, blank=True, verbose_name=_("Module args"))
-
- def __unicode__(self):
- return "%s" % self.name
-
-
-class Play(models.Model):
- """
- Playbook 模板, 定义好Template后生成 Playbook
- """
-
diff --git a/apps/ops/models/utils.py b/apps/ops/models/utils.py
deleted file mode 100644
index 7d9f13e3d..000000000
--- a/apps/ops/models/utils.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# ~*~ coding: utf-8 ~*~
-from __future__ import unicode_literals
-
-from ansible import *
-
-__all__ = ["generate_fake"]
-
-
-def generate_fake():
- for cls in (TaskRecord, AnsiblePlay, AnsibleTask, AnsibleHostResult):
- cls.generate_fake()
\ No newline at end of file
diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py
new file mode 100644
index 000000000..45de3e727
--- /dev/null
+++ b/apps/ops/tasks.py
@@ -0,0 +1,87 @@
+# coding: utf-8
+
+from __future__ import absolute_import, unicode_literals
+import time
+
+
+from django.utils import timezone
+from celery import shared_task
+
+from common.utils import get_logger, encrypt_password
+from .utils.runner import AdHocRunner
+from .models import TaskRecord
+
+logger = get_logger(__file__)
+
+
+@shared_task(name="get_assets_hardware_info")
+def get_assets_hardware_info(self, assets):
+ task_tuple = (
+ ('setup', ''),
+ )
+ hoc = AdHocRunner(assets)
+ return hoc.run(task_tuple)
+
+
+@shared_task(name="asset_test_ping_check")
+def asset_test_ping_check(assets):
+ task_tuple = (
+ ('ping', ''),
+ )
+ hoc = AdHocRunner(assets)
+ result = hoc.run(task_tuple)
+ return result['contacted'].keys(), result['dark'].keys()
+
+
+@shared_task(bind=True)
+def push_users(self, assets, users):
+ """
+ user: {
+ username: xxx,
+ shell: /bin/bash,
+ password: 'staf',
+ 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:
+ logger.debug('Push user: {}'.format(user))
+ # 添加用户, 设置公钥, 设置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',
+ "name=/etc/sudoers state=present regexp='^{0} ALL=(ALL)' "
+ "line='{0} ALL=(ALL) NOPASSWD: {1}' "
+ "validate='visudo -cf %s'".format(
+ user['username'], user.get('sudo', '/bin/whoami')
+ ))
+ ])
+ record = TaskRecord(name='Push user',
+ uuid=self.request.id,
+ date_start=timezone.now(),
+ assets=','.join(asset['hostname'] for asset in assets))
+ record.save()
+ logger.info('Runner start {0}'.format(timezone.now()))
+ hoc = AdHocRunner(assets)
+ _ = hoc.run(task_tuple)
+ logger.info('Runner complete {0}'.format(timezone.now()))
+ result_clean = hoc.clean_result()
+ record.date_finished = timezone.now()
+ record.is_finished = True
+
+ if len(result_clean['failed']) == 0:
+ record.is_success = True
+ else:
+ record.is_success = False
+ record.result = result_clean
+ record.save()
+ return result_clean
diff --git a/apps/ops/tasks/__init__.py b/apps/ops/tasks/__init__.py
deleted file mode 100644
index 6fe598964..000000000
--- a/apps/ops/tasks/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from taskers import *
\ No newline at end of file
diff --git a/apps/ops/tasks/_celery_tasks.py b/apps/ops/tasks/_celery_tasks.py
deleted file mode 100644
index 9c76fdc84..000000000
--- a/apps/ops/tasks/_celery_tasks.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from __future__ import absolute_import, unicode_literals
-
-from celery import shared_task
-
-from common import celery_app
-from ops.utils.ansible_api import Options, ADHocRunner
-
-
-@shared_task(name="get_asset_hardware_info")
-def get_asset_hardware_info(task_name, task_uuid, *assets):
- conf = Options()
- play_source = {
- "name": "Get host hardware information",
- "hosts": "default",
- "gather_facts": "no",
- "tasks": [
- dict(action=dict(module='setup'))
- ]
- }
- hoc = ADHocRunner(conf, play_source, *assets)
- ext_code, result = hoc.run(task_name, task_uuid)
- return ext_code, result
-
-
-@shared_task(name="asset_test_ping_check")
-def asset_test_ping_check(task_name, task_uuid, *assets):
- conf = Options()
- play_source = {
- "name": "Test host connection use ping",
- "hosts": "default",
- "gather_facts": "no",
- "tasks": [
- dict(action=dict(module='ping'))
- ]
- }
- hoc = ADHocRunner(conf, play_source, *assets)
- ext_code, result = hoc.run(task_name, task_uuid)
- return ext_code, result
-
-
-@shared_task(name="add_user_to_assert")
-def add_user_to_asset():
- pass
-
-
-@celery_app.task(name='hello-world')
-def hello():
- print('hello world!')
diff --git a/apps/ops/tasks/taskers.py b/apps/ops/tasks/taskers.py
deleted file mode 100644
index 3a724b049..000000000
--- a/apps/ops/tasks/taskers.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# ~*~ coding: utf-8 ~*~
-from __future__ import unicode_literals
-
-from ops.tasks import _celery_tasks
-
-from ops.models import TaskRecord
-from uuid import uuid1
-from celery.result import AsyncResult
-
-__all__ = ["get_result",
- "start_get_hardware_info",
- "start_ping_test",
- "get_hardware_info",
- "get_ping_test"]
-
-
-def get_result(task_id):
- result = AsyncResult(task_id)
- if result.ready():
- return {"Completed": True, "data": result.get()}
- else:
- return {"Completed": False, "data": None}
-
-
-def __get_result_by_tasker_id(tasker_uuid, deal_method):
- tasker = TaskRecord.objects.get(uuid=tasker_uuid)
- total = tasker.total_hosts
- total_len = len(total)
- host_results = []
-
- # 存储数据
- for play in tasker.plays.all():
- for t in play.tasks.all():
- task = {'name': t.name, 'uuid': t.uuid, 'percentage': 0, 'completed': {'success': {}, 'failed': {}}}
- completed = []
- count = 0
- for h in t.host_results.all():
- completed.append(h.name)
- count += 1
- if h.is_success:
- result = getattr(h, deal_method)
- if result.get('msg') is None:
- task['completed']['success'][h.name] = result.get('data')
- else:
- task['completed']['failed'][h.name] = result.get('msg')
- else:
- task['completed']['failed'][h.name] = h.failed_msg
-
- # 计算进度
- task['percentage'] = float(count * 100 / total_len)
- task['waited'] = list(set(total) - set(completed))
-
- host_results.append(task)
-
- return host_results
-
-
-def start_get_hardware_info(*assets):
- name = "Get host hardware information"
- uuid = "tasker-" + uuid1().hex
- _celery_tasks.get_asset_hardware_info.delay(name, uuid, *assets)
- return uuid
-
-
-def __get_hardware_info(tasker_uuid):
- return __get_result_by_tasker_id(tasker_uuid, 'deal_setup')
-
-
-def get_hardware_info(tasker_uuid):
- """
-
- :param assets: 资产列表
- :return: 返回数据结构样列
- {u'data': [{u'completed': {
- u'failed': {u'192.168.232.135': u'Authentication failure.'},
- u'success': {u'192.168.1.119': {u'cpu': u'GenuineIntel Intel Xeon E312xx (Sandy Bridge) 6\u6838',
- u'disk': {: },
- u'env': {: },
- u'interface': {: },
- u'mem': 3951,
- u'os': u'Ubuntu 16.04(xenial)',
- u'sn': u'NA'}}},
- u'name': u'',
- u'percentage': 100.0,
- u'uuid': u'87cfedfe-ba55-44ff-bc43-e7e73b869ca1',
- u'waited': []}
- ],
- u'msg': None}
- """
- try:
- return {"msg": None, "data": __get_hardware_info(tasker_uuid)}
- except Exception as e:
- return {"msg": "query data failed!, %s" % e.message, "data": None}
-
-
-def start_ping_test(*assets):
- name = "Test host connection"
- uuid = "tasker-" + uuid1().hex
- _celery_tasks.asset_test_ping_check.delay(name, uuid, *assets)
- return uuid
-
-
-def __get_ping_test(tasker_uuid):
- return __get_result_by_tasker_id(tasker_uuid, 'deal_ping')
-
-
-def get_ping_test(tasker_uuid):
- """
-
- :param assets: 资产列表
- :return: 返回数据结构样列
- {u'data': [{u'completed': {
- u'failed': {u'192.168.232.135': u'Authentication failure.'},
- u'success': {u'192.168.1.119': {u'success': True}}},
- u'name': u'',
- u'percentage': 100.0,
- u'uuid': u'3e6e0d3b-bee0-4383-b19e-bec6ba55d346',
- u'waited': []}
- ],
- u'msg': None}
- """
- try:
- return {"msg": None, "data": __get_ping_test(tasker_uuid)}
- except Exception as e:
- return {"msg": "query data failed!, %s" % e.message, "data": None}
-
diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py
index 4b19c3f1c..8a0fba4f7 100644
--- a/apps/ops/urls/view_urls.py
+++ b/apps/ops/urls/view_urls.py
@@ -9,8 +9,5 @@ __all__ = ["urlpatterns"]
urlpatterns = [
# TResource Task url
- url(r'^task/list$', page_view.TaskListView.as_view(), name='page-task-list'),
- url(r'^task/create$', page_view.TaskCreateView.as_view(), name='page-task-create'),
- url(r'^task/(?P[0-9]+)/detail$', page_view.TaskDetailView.as_view(), name='page-task-detail'),
- url(r'^task/(?P[0-9]+)/update$', page_view.TaskUpdateView.as_view(), name='page-task-update'),
+ url(r'^task/list$', page_view.TaskListView.as_view(), name='page-task-list'),
]
\ No newline at end of file
diff --git a/apps/ops/utils/runner.py b/apps/ops/utils/runner.py
index 1f84ad434..14ccf0afe 100644
--- a/apps/ops/utils/runner.py
+++ b/apps/ops/utils/runner.py
@@ -1,7 +1,7 @@
# ~*~ coding: utf-8 ~*~
+from __future__ import unicode_literals
import os
-import sys
from collections import namedtuple, defaultdict
from ansible.executor.task_queue_manager import TaskQueueManager
@@ -249,12 +249,20 @@ class AdHocRunner(object):
self.loader.cleanup_all_tmp_files()
def clean_result(self):
- result = defaultdict(dict)
- for host, msgs in self.results_callback.result_q['contacted'].items():
- result[host]['success'] = len(msgs)
+ """
+ :return: {
+ "success": ['hostname',],
+ "failed": [{'hostname': 'msg'}, {}],
+ }
+ """
+ result = {'success': [], 'failed': []}
+ for host in self.results_callback.result_q['contacted']:
+ result['success'].append(host)
for host, msgs in self.results_callback.result_q['dark'].items():
- result[host]['failed'] = len(msgs)
+ msg = '\n'.join(['{}: {}'.format(msg.get('invocation', {}).get('module_name'),
+ msg.get('msg', '')) for msg in msgs])
+ result['failed'].append({host: msg})
return result
diff --git a/apps/ops/views.py b/apps/ops/views.py
index c897cde79..ebaa2f033 100644
--- a/apps/ops/views.py
+++ b/apps/ops/views.py
@@ -2,18 +2,15 @@
from __future__ import unicode_literals
from django.conf import settings
-from django.views.generic.list import ListView, MultipleObjectMixin
-from django.views.generic.edit import CreateView, DeleteView, UpdateView
-from django.views.generic.detail import DetailView, SingleObjectMixin
+from django.views.generic.list import ListView
from users.utils import AdminUserRequiredMixin
-from ops.utils.mixins import CreateSudoPrivilegesMixin, ListSudoPrivilegesMixin
-from .models import Task
+from .models import TaskRecord
class TaskListView(AdminUserRequiredMixin, ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
- model = Task
+ model = TaskRecord
context_object_name = 'tasks'
template_name = 'task/list.html'
@@ -25,18 +22,3 @@ class TaskListView(AdminUserRequiredMixin, ListView):
kwargs.update(context)
return super(TaskListView, self).get_context_data(**kwargs)
-
-class TaskCreateView(AdminUserRequiredMixin, CreateView):
- model = Task
- template_name = 'task/create.html'
-
-
-class TaskUpdateView(AdminUserRequiredMixin, UpdateView):
- model = Task
- template_name = 'task/update.html'
-
-
-class TaskDetailView(DetailView):
- model = Task
- context_object_name = 'task'
- template_name = 'task/detail.html'
diff --git a/apps/perms/hands.py b/apps/perms/hands.py
index dd8e61090..cfcae6685 100644
--- a/apps/perms/hands.py
+++ b/apps/perms/hands.py
@@ -7,9 +7,4 @@ from assets.models import Asset, AssetGroup, SystemUser
from assets.serializers import AssetGrantedSerializer, AssetGroupSerializer
-def push_system_user(assets, system_user):
- print('Push system user %s' % system_user.name)
- for asset in assets:
- print('\tAsset: %s' % asset.ip)
-
diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html
index 8d7c10924..b2fcb1860 100644
--- a/apps/perms/templates/perms/asset_permission_create_update.html
+++ b/apps/perms/templates/perms/asset_permission_create_update.html
@@ -38,7 +38,7 @@
{{ form.user_groups|bootstrap_horizontal }}
{% trans 'Asset' %}
- {{ form.assets|bootstrap_horizontal }}
+ {{ form.assets|bootstrap_horizontal|safe }}
{{ form.asset_groups|bootstrap_horizontal }}
{{ form.system_users |bootstrap_horizontal }}
diff --git a/apps/perms/utils.py b/apps/perms/utils.py
index efad6cbce..a54c47f10 100644
--- a/apps/perms/utils.py
+++ b/apps/perms/utils.py
@@ -2,9 +2,11 @@
from __future__ import absolute_import, unicode_literals
-from common.utils import setattr_bulk
-from .hands import User, UserGroup, Asset, AssetGroup, SystemUser, \
- push_system_user
+from common.utils import setattr_bulk, get_logger
+from ops.tasks import push_users
+from .hands import User, UserGroup, Asset, AssetGroup, SystemUser
+
+logger = get_logger(__file__)
def get_user_group_granted_asset_groups(user_group):
@@ -220,6 +222,19 @@ def get_users_granted_in_asset_group(asset):
pass
+def push_system_user(assets, system_user):
+ logger.info('Push system user %s' % system_user.name)
+ for asset in assets:
+ logger.info('\tAsset: %s' % asset.ip)
+ if not assets:
+ return None
+
+ assets = [asset._to_secret_json() for asset in assets]
+ system_user = system_user._to_secret_json()
+ task = push_users.delay(assets, system_user)
+ return task.id
+
+
def associate_system_users_and_assets(system_users, assets, asset_groups):
"""关联系统用户和资产, 目的是保存它们的关系, 然后新加入的资产或系统
用户时,推送系统用户到资产
@@ -242,3 +257,5 @@ def associate_system_users_and_assets(system_users, assets, asset_groups):
)
system_user.assets.add(*(tuple(assets_all)))
push_system_user(assets_need_push, system_user)
+
+
diff --git a/apps/perms/views.py b/apps/perms/views.py
index b1aac2f9a..bce33f23a 100644
--- a/apps/perms/views.py
+++ b/apps/perms/views.py
@@ -1,9 +1,11 @@
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals, absolute_import
+
import functools
from django.utils.translation import ugettext as _
+from django.db import transaction
from django.conf import settings
from django.db.models import Q
from django.views.generic import ListView, CreateView, UpdateView
@@ -65,6 +67,10 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
template_name = 'perms/asset_permission_create_update.html'
success_url = reverse_lazy('perms:asset-permission-list')
+ @transaction.atomic
+ def post(self, request, *args, **kwargs):
+ return super(AssetPermissionCreateView, self).post(request, *args, **kwargs)
+
def get_context_data(self, **kwargs):
context = {
'app': _('Perms'),
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index e3700f963..bc82d5b0b 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -19,3 +19,4 @@ itsdangerous==0.24
tornado==4.4.2
eventlet==0.20.1
django-filter==1.0.0
+passlib==1.7.1
diff --git a/run_server.py b/run_server.py
index 384db3282..9f11de4cf 100644
--- a/run_server.py
+++ b/run_server.py
@@ -29,8 +29,9 @@ def start_django():
def start_celery():
os.chdir(apps_dir)
os.environ.setdefault('C_FORCE_ROOT', '1')
+ os.environ.setdefault('PYTHONOPTIMIZE', 1)
print('start celery')
- subprocess.call('celery -A common worker -P eventlet -s /tmp/celerybeat-schedule -l info ', shell=True)
+ subprocess.call('celery -A common worker -s /tmp/celerybeat-schedule -l debug', shell=True)
def main():