mirror of https://github.com/jumpserver/jumpserver
[Fixture] 完成用户推送task
parent
c234b5b2d5
commit
a822f667af
|
@ -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):
|
||||||
# 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:
|
||||||
|
key_string = self.cleaned_data['private_key_file'].read()
|
||||||
|
self.cleaned_data['private_key_file'].seek(0)
|
||||||
|
if not validate_ssh_private_key(key_string):
|
||||||
|
raise forms.ValidationError(_('Private key invalid'))
|
||||||
|
return self.cleaned_data['private_key_file']
|
||||||
|
|
||||||
# def clean_password(self):
|
def clean_password(self):
|
||||||
# if self.cleaned_data['auth_method'] == 'P':
|
if self.data['auth_method'] == 'P':
|
||||||
# if not self.cleaned_data['password']:
|
if not self.cleaned_data.get('password'):
|
||||||
# raise forms.ValidationError(_('Password required'))
|
raise forms.ValidationError(_('Password required'))
|
||||||
# return self.cleaned_data['password']
|
return self.cleaned_data['password']
|
||||||
|
|
||||||
# def clean(self):
|
|
||||||
# password = self.cleaned_data['password']
|
|
||||||
# private_key_file = self.cleaned_data.get('private_key_file', '')
|
|
||||||
#
|
|
||||||
# if not (password or private_key_file):
|
|
||||||
# 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):
|
||||||
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):
|
||||||
)
|
)
|
||||||
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):
|
||||||
|
|
||||||
@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):
|
||||||
|
|
||||||
@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):
|
||||||
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 @@
|
||||||
{% 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 @@
|
||||||
{{ 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 @@
|
||||||
$('.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 @@
|
||||||
$(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 %}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends 'assets/_system_user.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block auth %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% 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
|
||||||
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):
|
||||||
})
|
})
|
||||||
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
|
||||||
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):
|
||||||
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
|
||||||
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
|
||||||
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):
|
||||||
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()
|
|
@ -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(',')
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
from ansible import *
|
|
||||||
from utils import *
|
|
||||||
from task import *
|
|
||||||
|
|
|
@ -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: <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
|
|
|
@ -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
|
|
||||||
"""
|
|
||||||
|
|
|
@ -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()
|
|
|
@ -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
|
|
@ -1 +0,0 @@
|
||||||
from taskers import *
|
|
|
@ -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!')
|
|
|
@ -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': {<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"]
|
||||||
|
|
||||||
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'),
|
|
||||||
]
|
]
|
|
@ -1,7 +1,7 @@
|
||||||
# ~*~ 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):
|
||||||
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 @@
|
||||||
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):
|
||||||
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
|
||||||
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 @@
|
||||||
{{ 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 @@
|
||||||
|
|
||||||
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):
|
||||||
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):
|
||||||
)
|
)
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
# ~*~ 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,
|
||||||
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
|
||||||
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():
|
||||||
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():
|
||||||
|
|
Loading…
Reference in New Issue