提交 a822f667 编写于 作者: baltery's avatar baltery

[Fixture] 完成用户推送task

上级 c234b5b2
...@@ -3,7 +3,10 @@ from django import forms ...@@ -3,7 +3,10 @@ from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .models import IDC, Asset, AssetGroup, AdminUser, SystemUser, Tag from .models import IDC, Asset, AssetGroup, AdminUser, SystemUser, Tag
from common.utils import validate_ssh_private_key, ssh_pubkey_gen from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, get_logger
logger = get_logger(__file__)
class AssetCreateForm(forms.ModelForm): class AssetCreateForm(forms.ModelForm):
...@@ -207,59 +210,59 @@ class SystemUserForm(forms.ModelForm): ...@@ -207,59 +210,59 @@ class SystemUserForm(forms.ModelForm):
# Admin user assets define, let user select, save it in form not in view # Admin user assets define, let user select, save it in form not in view
auto_generate_key = forms.BooleanField(initial=True, required=False) auto_generate_key = forms.BooleanField(initial=True, required=False)
# Form field name can not start with `_`, so redefine it, # Form field name can not start with `_`, so redefine it,
password = forms.CharField(widget=forms.PasswordInput, max_length=100, min_length=8, strip=True) password = forms.CharField(widget=forms.PasswordInput, required=False,
max_length=100, min_length=8, strip=True)
# Need use upload private key file except paste private key content # Need use upload private key file except paste private key content
private_key_file = forms.FileField(required=False) private_key_file = forms.FileField(required=False)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# When update a admin user instance, initial it
if kwargs.get('instance'):
initial = kwargs.get('initial', {})
initial['assets'] = kwargs['instance'].assets.all()
initial['asset_groups'] = kwargs['instance'].asset_groups.all()
super(SystemUserForm, self).__init__(*args, **kwargs) super(SystemUserForm, self).__init__(*args, **kwargs)
def save(self, commit=True): def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save` # Because we define custom field, so we need rewrite :method: `save`
system_user = super(SystemUserForm, self).save(commit=commit) system_user = super(SystemUserForm, self).save(commit=commit)
password = self.cleaned_data['password'] password = self.cleaned_data['password']
private_key_file = self.cleaned_data['private_key_file'] private_key_file = self.cleaned_data.get('private_key_file')
if system_user.auth_method == 'P': if system_user.auth_method == 'P':
if password: if password:
system_user.password = password system_user.password = password
print(password) elif system_user.auth_method == 'K':
# Todo: Validate private key file, and generate public key if self.cleaned_data['auto_generate_key']:
# Todo: Auto generate private key and public key private_key, public_key = ssh_key_gen(username=system_user.name)
if private_key_file: logger.info('Generate private key and public key')
system_user.private_key = private_key_file.read().strip() else:
private_key = private_key_file.read().strip()
public_key = ssh_pubkey_gen(private_key=private_key)
system_user.private_key = private_key
system_user.public_key = public_key
system_user.save() system_user.save()
return self.instance return self.instance
# Todo: check valid def clean_private_key_file(self):
# def clean_private_key_file(self): if self.data['auth_method'] == 'K' and \
# if not self.cleaned_data['auto_generate_key']: not self.cleaned_data['auto_generate_key']:
# if not self.cleaned_data['private_key_file']: if not self.cleaned_data['private_key_file']:
# raise forms.ValidationError(_('Private key required')) raise forms.ValidationError(_('Private key required'))
else:
# def clean_password(self): key_string = self.cleaned_data['private_key_file'].read()
# if self.cleaned_data['auth_method'] == 'P': self.cleaned_data['private_key_file'].seek(0)
# if not self.cleaned_data['password']: if not validate_ssh_private_key(key_string):
# raise forms.ValidationError(_('Password required')) raise forms.ValidationError(_('Private key invalid'))
# return self.cleaned_data['password'] return self.cleaned_data['private_key_file']
# def clean(self): def clean_password(self):
# password = self.cleaned_data['password'] if self.data['auth_method'] == 'P':
# private_key_file = self.cleaned_data.get('private_key_file', '') if not self.cleaned_data.get('password'):
# raise forms.ValidationError(_('Password required'))
# if not (password or private_key_file): return self.cleaned_data['password']
# raise forms.ValidationError(_('Password and private key file must be input one'))
class Meta: class Meta:
model = SystemUser model = SystemUser
fields = [ fields = [
'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'auth_method', 'name', 'username', 'protocol', 'auto_generate_key', 'password',
'auto_push', 'sudo', 'comment', 'shell', 'home', 'uid', 'private_key_file', 'auth_method', 'auto_push', 'sudo',
'comment', 'shell', 'home', 'uid',
] ]
widgets = { widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'name': forms.TextInput(attrs={'placeholder': _('Name')}),
......
...@@ -71,7 +71,7 @@ class Asset(models.Model): ...@@ -71,7 +71,7 @@ class Asset(models.Model):
tags = models.ManyToManyField('Tag', related_name='assets', blank=True, verbose_name=_('Tags')) tags = models.ManyToManyField('Tag', related_name='assets', blank=True, verbose_name=_('Tags'))
def __unicode__(self): def __unicode__(self):
return '%(ip)s:%(port)s' % {'ip': self.ip, 'port': self.port} return '%s <%s: %s>' % (self.hostname, self.ip, self.port)
__str__ = __unicode__ __str__ = __unicode__
......
...@@ -116,14 +116,13 @@ class SystemUser(models.Model): ...@@ -116,14 +116,13 @@ class SystemUser(models.Model):
) )
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
username = models.CharField(max_length=16, verbose_name=_('Username')) username = models.CharField(max_length=16, verbose_name=_('Username'))
_password = models.CharField(max_length=256, blank=True, verbose_name=_('Password')) _password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
_private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key')) _private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key'))
_public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key')) _public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K', auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K',
max_length=1, verbose_name=_('Auth method')) max_length=1, verbose_name=_('Auth method'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
auto_update = models.BooleanField(default=True, verbose_name=_('Auto update pass/key'))
sudo = models.TextField(max_length=4096, default='/user/bin/whoami', verbose_name=_('Sudo')) sudo = models.TextField(max_length=4096, default='/user/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
home = models.CharField(max_length=64, blank=True, verbose_name=_('Home')) home = models.CharField(max_length=64, blank=True, verbose_name=_('Home'))
...@@ -137,7 +136,9 @@ class SystemUser(models.Model): ...@@ -137,7 +136,9 @@ class SystemUser(models.Model):
@property @property
def password(self): def password(self):
return signer.unsign(self._password) if self._password:
return signer.unsign(self._password)
return None
@password.setter @password.setter
def password(self, password_raw): def password(self, password_raw):
...@@ -145,7 +146,9 @@ class SystemUser(models.Model): ...@@ -145,7 +146,9 @@ class SystemUser(models.Model):
@property @property
def private_key(self): def private_key(self):
return signer.unsign(self._private_key) if self._private_key:
return signer.unsign(self._private_key)
return None
@private_key.setter @private_key.setter
def private_key(self, private_key_raw): def private_key(self, private_key_raw):
...@@ -174,6 +177,17 @@ class SystemUser(models.Model): ...@@ -174,6 +177,17 @@ class SystemUser(models.Model):
assets = set(self.assets.all()) | self.get_assets_inherit_from_asset_groups() assets = set(self.assets.all()) | self.get_assets_inherit_from_asset_groups()
return list(assets) return list(assets)
def _to_secret_json(self):
"""Push system user use it"""
return {
'name': self.name,
'username': self.username,
'shell': self.shell,
'sudo': self.sudo,
'password': self.password,
'public_key': self.public_key
}
@property @property
def assets_amount(self): def assets_amount(self):
return self.assets.count() return self.assets.count()
......
...@@ -34,14 +34,20 @@ ...@@ -34,14 +34,20 @@
{% endif %} {% endif %}
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" > <form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
{% csrf_token %} {% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<h3>{% trans 'Basic' %}</h3> <h3>{% trans 'Basic' %}</h3>
{{ form.name|bootstrap_horizontal }} {{ form.name|bootstrap_horizontal }}
{{ form.username|bootstrap_horizontal }} {{ form.username|bootstrap_horizontal }}
{{ form.protocol|bootstrap_horizontal }} {{ form.protocol|bootstrap_horizontal }}
<h3>{% trans 'Auth' %}</h3> <h3>{% trans 'Auth' %}</h3>
{{ form.auth_method|bootstrap_horizontal }} {{ form.auth_method|bootstrap_horizontal }}
{% block auth %}
<div class="password-auth hidden"> <div class="password-auth hidden">
{{ form.password|bootstrap_horizontal }} {{ form.password|bootstrap_horizontal }}
</div> </div>
<div class="public-key-auth"> <div class="public-key-auth">
<div class="form-group"> <div class="form-group">
...@@ -54,6 +60,7 @@ ...@@ -54,6 +60,7 @@
{{ form.private_key_file|bootstrap_horizontal }} {{ form.private_key_file|bootstrap_horizontal }}
</div> </div>
</div> </div>
{% endblock %}
<div class="form-group"> <div class="form-group">
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label> <label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
<div class="col-sm-8"> <div class="col-sm-8">
...@@ -88,16 +95,19 @@ ...@@ -88,16 +95,19 @@
$('.password-auth').removeClass('hidden'); $('.password-auth').removeClass('hidden');
$('.public-key-auth').addClass('hidden'); $('.public-key-auth').addClass('hidden');
$('#'+'{{ form.password.id_for_label }}').attr('required', 'required'); $('#'+'{{ form.password.id_for_label }}').attr('required', 'required');
$('#'+'{{ form.password.id_for_label }}').removeAttr('disabled');
} else if ($(auth_method).val() == 'K') { } else if ($(auth_method).val() == 'K') {
$('.password-auth').addClass('hidden'); $('.password-auth').addClass('hidden');
$('.public-key-auth').removeClass('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')){ if ($(auto_generate_key).prop('checked')){
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden'); $('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden');
} else { } else {
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').removeClass('hidden'); $('#'+'{{ 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 @@ ...@@ -110,14 +120,8 @@
$(auto_generate_key).change(function () { $(auto_generate_key).change(function () {
authMethodDisplay(); 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();
}
})
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
{% extends 'assets/_system_user.html' %}
{% load i18n %}
{% load static %}
{% block auth %}
{{ block.super }}
{% endblock %}
{% extends 'assets/_system_user.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap %}
{% block auth %}
<div class="password-auth hidden">
{{ form.password|bootstrap_horizontal }}
</div>
<div class="public-key-auth">
<div>
{{ form.private_key_file|bootstrap_horizontal }}
</div>
</div>
{% endblock %}
...@@ -4,6 +4,7 @@ from rest_framework import serializers ...@@ -4,6 +4,7 @@ from rest_framework import serializers
from models import Tag from models import Tag
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
class CreateAssetTagsMiXin(CreateView): class CreateAssetTagsMiXin(CreateView):
def get_form_kwargs(self): def get_form_kwargs(self):
tags_list = self.request.POST.getlist('tags') tags_list = self.request.POST.getlist('tags')
...@@ -30,6 +31,7 @@ class CreateAssetTagsMiXin(CreateView): ...@@ -30,6 +31,7 @@ class CreateAssetTagsMiXin(CreateView):
}) })
return kwargs return kwargs
class UpdateAssetTagsMiXin(CreateAssetTagsMiXin): class UpdateAssetTagsMiXin(CreateAssetTagsMiXin):
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(UpdateAssetTagsMiXin, self).get_form_kwargs() kwargs = super(UpdateAssetTagsMiXin, self).get_form_kwargs()
......
...@@ -9,6 +9,7 @@ from openpyxl import load_workbook ...@@ -9,6 +9,7 @@ from openpyxl import load_workbook
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
from django.db.models import Q from django.db.models import Q
from django.db import transaction
from django.db import IntegrityError from django.db import IntegrityError
from django.views.generic import TemplateView, ListView, View from django.views.generic import TemplateView, ListView, View
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
...@@ -521,9 +522,13 @@ class SystemUserListView(AdminUserRequiredMixin, TemplateView): ...@@ -521,9 +522,13 @@ class SystemUserListView(AdminUserRequiredMixin, TemplateView):
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = SystemUser model = SystemUser
form_class = forms.SystemUserForm 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') 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): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
...@@ -549,7 +554,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi ...@@ -549,7 +554,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView): class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView):
model = SystemUser model = SystemUser
form_class = forms.SystemUserForm 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): def get_context_data(self, **kwargs):
context = { context = {
......
...@@ -16,6 +16,7 @@ import calendar ...@@ -16,6 +16,7 @@ import calendar
import threading import threading
import paramiko import paramiko
from passlib.hash import sha512_crypt
import sshpubkeys import sshpubkeys
from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \ from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \
BadSignature, SignatureExpired BadSignature, SignatureExpired
...@@ -322,4 +323,11 @@ def make_signature(access_key_secret, date=None): ...@@ -322,4 +323,11 @@ def make_signature(access_key_secret, date=None):
return content_md5(data) 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() signer = Signer()
\ No newline at end of file
# ~*~ 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(',')
from ansible import *
from utils import *
from task import *
# ~*~ 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: <list> 一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃sr0子类的 ['sr']
:return: <dict> 过滤获取的结果
"""
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: <list> 一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃lo子类的 ['lo']
:return: <dict> 过滤获取的结果
"""
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: <dict> 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: <dict> {"msg": <str>, "data": <dict>}, 注意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: <dict> {"msg": <str>, "data": {"success": <bool>}}, 注意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
# ~*~ 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
"""
# ~*~ 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
# 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
from taskers import *
\ No newline at end of file
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!')
# ~*~ 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': {<device_name>: <device_detail_dict>},
u'env': {<env_name>: <env_value>},
u'interface': {<interface_name>: <interface_detail_dict>},
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}
...@@ -9,8 +9,5 @@ __all__ = ["urlpatterns"] ...@@ -9,8 +9,5 @@ __all__ = ["urlpatterns"]
urlpatterns = [ urlpatterns = [
# TResource Task url # TResource Task url
url(r'^task/list$', page_view.TaskListView.as_view(), name='page-task-list'), 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<pk>[0-9]+)/detail$', page_view.TaskDetailView.as_view(), name='page-task-detail'),
url(r'^task/(?P<pk>[0-9]+)/update$', page_view.TaskUpdateView.as_view(), name='page-task-update'),
] ]
\ No newline at end of file
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
import os import os
import sys
from collections import namedtuple, defaultdict from collections import namedtuple, defaultdict
from ansible.executor.task_queue_manager import TaskQueueManager from ansible.executor.task_queue_manager import TaskQueueManager
...@@ -249,12 +249,20 @@ class AdHocRunner(object): ...@@ -249,12 +249,20 @@ class AdHocRunner(object):
self.loader.cleanup_all_tmp_files() self.loader.cleanup_all_tmp_files()
def clean_result(self): def clean_result(self):
result = defaultdict(dict) """
for host, msgs in self.results_callback.result_q['contacted'].items(): :return: {
result[host]['success'] = len(msgs) "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(): 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 return result
......
...@@ -2,18 +2,15 @@ ...@@ -2,18 +2,15 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.views.generic.list import ListView, MultipleObjectMixin from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.detail import DetailView, SingleObjectMixin
from users.utils import AdminUserRequiredMixin from users.utils import AdminUserRequiredMixin
from ops.utils.mixins import CreateSudoPrivilegesMixin, ListSudoPrivilegesMixin from .models import TaskRecord
from .models import Task
class TaskListView(AdminUserRequiredMixin, ListView): class TaskListView(AdminUserRequiredMixin, ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
model = Task model = TaskRecord
context_object_name = 'tasks' context_object_name = 'tasks'
template_name = 'task/list.html' template_name = 'task/list.html'
...@@ -25,18 +22,3 @@ class TaskListView(AdminUserRequiredMixin, ListView): ...@@ -25,18 +22,3 @@ class TaskListView(AdminUserRequiredMixin, ListView):
kwargs.update(context) kwargs.update(context)
return super(TaskListView, self).get_context_data(**kwargs) 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'
...@@ -7,9 +7,4 @@ from assets.models import Asset, AssetGroup, SystemUser ...@@ -7,9 +7,4 @@ from assets.models import Asset, AssetGroup, SystemUser
from assets.serializers import AssetGrantedSerializer, AssetGroupSerializer 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)
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
{{ form.user_groups|bootstrap_horizontal }} {{ form.user_groups|bootstrap_horizontal }}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans 'Asset' %}</h3> <h3>{% trans 'Asset' %}</h3>
{{ form.assets|bootstrap_horizontal }} {{ form.assets|bootstrap_horizontal|safe }}
{{ form.asset_groups|bootstrap_horizontal }} {{ form.asset_groups|bootstrap_horizontal }}
{{ form.system_users |bootstrap_horizontal }} {{ form.system_users |bootstrap_horizontal }}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
......
...@@ -2,9 +2,11 @@ ...@@ -2,9 +2,11 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from common.utils import setattr_bulk from common.utils import setattr_bulk, get_logger
from .hands import User, UserGroup, Asset, AssetGroup, SystemUser, \ from ops.tasks import push_users
push_system_user from .hands import User, UserGroup, Asset, AssetGroup, SystemUser
logger = get_logger(__file__)
def get_user_group_granted_asset_groups(user_group): def get_user_group_granted_asset_groups(user_group):
...@@ -220,6 +222,19 @@ def get_users_granted_in_asset_group(asset): ...@@ -220,6 +222,19 @@ def get_users_granted_in_asset_group(asset):
pass 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): 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): ...@@ -242,3 +257,5 @@ def associate_system_users_and_assets(system_users, assets, asset_groups):
) )
system_user.assets.add(*(tuple(assets_all))) system_user.assets.add(*(tuple(assets_all)))
push_system_user(assets_need_push, system_user) push_system_user(assets_need_push, system_user)
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
import functools import functools
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.db import transaction
from django.conf import settings from django.conf import settings
from django.db.models import Q from django.db.models import Q
from django.views.generic import ListView, CreateView, UpdateView from django.views.generic import ListView, CreateView, UpdateView
...@@ -65,6 +67,10 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, ...@@ -65,6 +67,10 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
template_name = 'perms/asset_permission_create_update.html' template_name = 'perms/asset_permission_create_update.html'
success_url = reverse_lazy('perms:asset-permission-list') 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): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Perms'), 'app': _('Perms'),
......
...@@ -19,3 +19,4 @@ itsdangerous==0.24 ...@@ -19,3 +19,4 @@ itsdangerous==0.24
tornado==4.4.2 tornado==4.4.2
eventlet==0.20.1 eventlet==0.20.1
django-filter==1.0.0 django-filter==1.0.0
passlib==1.7.1
...@@ -29,8 +29,9 @@ def start_django(): ...@@ -29,8 +29,9 @@ def start_django():
def start_celery(): def start_celery():
os.chdir(apps_dir) os.chdir(apps_dir)
os.environ.setdefault('C_FORCE_ROOT', '1') os.environ.setdefault('C_FORCE_ROOT', '1')
os.environ.setdefault('PYTHONOPTIMIZE', 1)
print('start celery') 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(): def main():
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册