Commit 2b3551f1 authored by ibuler's avatar ibuler

[Feature] 修改ansible和ops

parent e57121a7
......@@ -44,28 +44,18 @@ class Asset(models.Model):
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets',
verbose_name=_('Asset groups'))
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets',
on_delete=models.SET_NULL, verbose_name=_("Admin user"))
system_users = models.ManyToManyField(SystemUser, blank=True,
related_name='assets',
verbose_name=_("System User"))
idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets',
on_delete=models.SET_NULL, verbose_name=_('IDC'),)
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_("Admin user"))
system_users = models.ManyToManyField(SystemUser, blank=True, related_name='assets', verbose_name=_("System User"))
idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('IDC'),)
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
default='Server', verbose_name=_('Asset type'),)
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True,
default='Prod', verbose_name=_('Asset environment'),)
status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True,
default='In use', verbose_name=_('Asset status'))
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),)
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),)
status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True, default='In use', verbose_name=_('Asset status'))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True,
null=True, verbose_name=_('Public IP'))
remote_card_ip = models.CharField(max_length=16, null=True, blank=True,
verbose_name=_('Remote control card IP'))
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote control card IP'))
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
......@@ -114,22 +104,27 @@ class Asset(models.Model):
def _to_secret_json(self):
"""Ansible use it create inventory"""
return {
data = {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
'groups': [group.name for group in self.groups.all()],
'username': self.admin_user.username if self.admin_user else '',
'password': self.admin_user.password if self.admin_user else '',
'private_key': self.admin_user.private_key_file if self.admin_user else None,
'become': {
'method': self.admin_user.become_method,
'user': self.admin_user.become_user,
'pass': self.admin_user.become_pass,
} if self.admin_user and self.admin_user.become else {},
}
if self.admin_user:
data.update({
'username': self.admin_user.username,
'password': self.admin_user.password,
'private_key': self.admin_user.private_key_file,
'become': {
'method': self.admin_user.become_method,
'user': self.admin_user.become_user,
'pass': self.admin_user.become_pass,
}
})
return data
class Meta:
unique_together = ('ip', 'port')
......
......@@ -28,6 +28,9 @@ def private_key_validator(value):
class AdminUser(models.Model):
"""
Ansible use admin user as devops user to run adHoc and Playbook
"""
BECOME_METHOD_CHOICES = (
('sudo', 'sudo'),
('su', 'su'),
......@@ -35,24 +38,19 @@ class AdminUser(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
username = models.CharField(max_length=16, verbose_name=_('Username'))
_password = models.CharField(
max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'),
validators=[private_key_validator,])
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator,])
become = models.BooleanField(default=True)
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64)
become_pass = models.CharField(default='', max_length=128)
_public_key = models.TextField(
max_length=4096, blank=True, verbose_name=_('SSH public key'))
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, null=True)
created_by = models.CharField(
max_length=32, null=True, verbose_name=_('Created by'))
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
def __unicode__(self):
def __str__(self):
return self.name
__str__ = __unicode__
@property
def password(self):
......@@ -134,33 +132,22 @@ class SystemUser(models.Model):
('K', 'Public key'),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
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'))
_password = models.CharField(
max_length=256, blank=True, verbose_name=_('Password'))
protocol = models.CharField(
max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
_private_key = models.TextField(
max_length=8192, blank=True, verbose_name=_('SSH private key'))
_public_key = models.TextField(
max_length=8192, blank=True, verbose_name=_('SSH public key'))
auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K',
max_length=1, verbose_name=_('Auth method'))
_password = models.CharField(max_length=256, blank=True, verbose_name=_('Password'))
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
_private_key = models.TextField(max_length=8192, blank=True, verbose_name=_('SSH private key'))
_public_key = models.TextField(max_length=8192, blank=True, verbose_name=_('SSH public key'))
auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K', max_length=1, verbose_name=_('Auth method'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(
max_length=4096, default='/sbin/ifconfig', verbose_name=_('Sudo'))
shell = models.CharField(
max_length=64, default='/bin/bash', verbose_name=_('Shell'))
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
date_created = models.DateTimeField(auto_now_add=True)
created_by = models.CharField(
max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(
max_length=128, blank=True, verbose_name=_('Comment'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(max_length=128, blank=True, verbose_name=_('Comment'))
def __unicode__(self):
def __str__(self):
return self.name
__str__ = __unicode__
@property
def password(self):
......@@ -182,9 +169,24 @@ class SystemUser(models.Model):
def private_key(self, private_key_raw):
self._private_key = signer.sign(private_key_raw)
@property
def private_key_file(self):
if not self.private_key:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_name = md5(self._private_key.encode()).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key.write_private_key_file(key_path)
return key_path
@property
def public_key(self):
return signer.unsign(self._public_key)
if self._public_key:
return signer.unsign(self._public_key)
else:
return None
@public_key.setter
def public_key(self, public_key_raw):
......@@ -213,7 +215,8 @@ class SystemUser(models.Model):
'shell': self.shell,
'sudo': self.sudo,
'password': self.password,
'public_key': self.public_key
'public_key': self.public_key,
'private_key_file': self.private_key_file,
}
@property
......
......@@ -19,3 +19,32 @@ def test_admin_user_connective_manual(asset):
def get_assets_by_id_list(id_list):
return Asset.objects.filter(id__in=id_list)
def get_assets_by_hostname_list(hostname_list):
return Asset.objects.filter(hostname__in=hostname_list)
def get_asset_admin_user(user, asset):
if user.is_superuser:
return asset.admin_user
else:
msg = "{} have no permission for admin user".format(user.username)
raise PermissionError(msg)
def get_asset_system_user(user, asset, system_user_name):
from perms.utils import get_user_granted_assets
assets = get_user_granted_assets(user)
system_users = {system_user.name: system_user for system_user in assets.get(asset)}
if system_user_name in system_users:
return system_users[system_user_name]
else:
msg = "{} have no permission for {}".format(user.name, system_user_name)
raise PermissionError(msg)
def get_assets_with_admin_by_hostname_list(hostname_list):
assets = Asset.objects.filter(hostname__in=hostname_list)
return [(asset, asset.admin_user) for asset in assets]
# -*- coding: utf-8 -*-
#
......@@ -9,65 +9,85 @@ class AdHocResultCallback(CallbackBase):
"""
def __init__(self, display=None):
# result_raw example: {
# "ok": {"hostname": []},
# "failed": {"hostname": []},
# "unreachable: {"hostname": []},
# "skipped": {"hostname": []},
# "ok": {"hostname": [{"task_name": {},...],..},
# "failed": {"hostname": ["task_name": {}..], ..},
# "unreachable: {"hostname": ["task_name": {}, ..]},
# "skipped": {"hostname": ["task_name": {}, ..], ..},
# }
# results_summary example: {
# "contacted": {"hostname",...},
# "dark": {"hostname": ["error",...],},
# "dark": {"hostname": [{"task_name": "error"},...],},
# }
self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={})
self.results_summary = dict(contacted=set(), dark={})
super().__init__(display)
def gather_result(self, t, host, res):
def gather_result(self, t, res):
host = res._host.get_name()
task_name = res.task_name
task_result = res._result
if self.results_raw[t].get(host):
self.results_raw[t][host].append(res)
self.results_raw[t][host].append({task_name: task_result})
else:
self.results_raw[t][host] = [res]
self.clean_result(t, host, res)
self.results_raw[t][host] = [{task_name: task_result}]
self.clean_result(t, host, task_name, task_result)
def clean_result(self, t, host, res):
def clean_result(self, t, host, task_name, task_result):
contacted = self.results_summary["contacted"]
dark = self.results_summary["dark"]
if t in ("ok", "skipped") and host not in dark:
contacted.add(host)
else:
dark[host].append(res)
if dark.get(host):
dark[host].append({task_name: task_result})
else:
dark[host] = [{task_name: task_result}]
if host in contacted:
contacted.remove(dark)
def runner_on_ok(self, host, res):
self.gather_result("ok", host, res)
def v2_runner_on_failed(self, result, ignore_errors=False):
self.gather_result("failed", result)
def runner_on_failed(self, host, res, ignore_errors=False):
self.gather_result("failed", host, res)
def v2_runner_on_ok(self, result):
self.gather_result("ok", result)
def runner_on_unreachable(self, host, res):
self.gather_result("unreachable", host, res)
def v2_runner_on_skipped(self, result):
self.gather_result("skipped", result)
def runner_on_skipped(self, host, item=None):
self.gather_result("skipped", host, item)
def v2_runner_on_unreachable(self, result):
self.gather_result("unreachable", result)
class CommandResultCallback(AdHocResultCallback):
"""
Command result callback
"""
def __init__(self, display=None):
# results_command: {
# "cmd": "",
# "stderr": "",
# "stdout": "",
# "rc": 0,
# "delta": 0:0:0.123
# }
#
self.results_command = dict()
super().__init__(display)
def gather_result(self, t, host, res):
super().gather_result(t, host, res)
self.gather_cmd(t, host, res)
def gather_result(self, t, res):
super().gather_result(t, res)
self.gather_cmd(t, res)
def gather_cmd(self, t, host, res):
def gather_cmd(self, t, res):
host = res._host.get_name()
cmd = {}
if t == "ok":
cmd['cmd'] = res.get('cmd')
cmd['stderr'] = res.get('stderr')
cmd['stdout'] = res.get('stdout')
cmd['rc'] = res.get('rc')
cmd['cmd'] = res._result.get('cmd')
cmd['stderr'] = res._result.get('stderr')
cmd['stdout'] = res._result.get('stdout')
cmd['rc'] = res._result.get('rc')
cmd['delta'] = res._result.get('delta')
else:
cmd['err'] = "Error: {}".format(res)
self.results_command[host] = cmd
......
......@@ -170,6 +170,10 @@ class AdHocRunner:
"pattern: %s dose not match any hosts." % pattern
)
def set_option(self, k, v):
kwargs = {k: v}
self.options = self.options._replace(**kwargs)
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
"""
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]
......
......@@ -24,8 +24,8 @@ class TestAdHocRunner(unittest.TestCase):
def test_run(self):
tasks = [
{"action": {"module": "shell", "args": "ls"}},
{"action": {"module": "shell", "args": "whoami"}},
{"action": {"module": "shell", "args": "ls"}, "name": "run_cmd"},
{"action": {"module": "shell", "args": "whoami"}, "name": "run_whoami"},
]
ret = self.runner.run(tasks, "all")
print(ret.results_summary)
......@@ -48,6 +48,7 @@ class TestCommandRunner(unittest.TestCase):
def test_execute(self):
res = self.runner.execute('ls', 'all')
print(res.results_command)
print(res.results_raw)
if __name__ == "__main__":
......
......@@ -4,12 +4,12 @@
from rest_framework import viewsets
from .hands import IsSuperUser
from .models import Playbook
from .models import AdHoc
from .serializers import TaskSerializer
class TaskViewSet(viewsets.ModelViewSet):
queryset = Playbook.objects.all()
queryset = AdHoc.objects.all()
serializer_class = TaskSerializer
permission_classes = (IsSuperUser,)
# ~*~ coding: utf-8 ~*~
import logging
from collections import OrderedDict
import json
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
__all__ = ["Playbook"]
__all__ = ["AdHoc", "History"]
logger = logging.getLogger(__name__)
class AdHoc(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, primary_key=True)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
tasks = models.TextField(verbose_name=_('Tasks')) # [{'name': 'task_name', 'module': '', 'args': ''}, ]
hosts = models.TextField(blank=True, null=True, verbose_name=_('Hosts')) # Asset inventory may be change
pattern = models.CharField(max_length=64, default='all', verbose_name=_('Playbook run pattern'))
@property
def short_id(self):
return str(self.id).split('-')[-1]
def __str__(self):
return "%s" % self.name
return self.name
def get_hosts_mapped_assets(self):
from assets.utils import get_assets_by_id_list
assets_id = [i for i in self.hosts.split(',')]
assets = get_assets_by_id_list(assets_id)
return assets
class AdHocData(models.Model):
BECOME_METHOD_CHOICES = (
('sudo', 'sudo'),
('su', 'su'),
)
version = models.UUIDField(default=uuid.uuid4, primary_key=True)
subject = models.ForeignKey(AdHoc, on_delete=models.CASCADE)
_tasks = models.TextField(verbose_name=_('Tasks')) # [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ]
_hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2']
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
run_as = models.CharField(max_length=128, verbose_name=_("Run as"))
become = models.BooleanField(default=False, verbose_name=_("Become"))
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64)
become_pass = models.CharField(default='', max_length=128)
pattern = models.CharField(max_length=64, default='', verbose_name=_('Pattern'))
created_by = models.CharField(max_length=64, verbose_name=_('Create by'))
date_created = models.DateTimeField(auto_created=True)
@property
def inventory(self):
return [asset._to_secret_json() for asset in self.get_hosts_mapped_assets()]
def tasks(self):
return json.loads(self._tasks)
@tasks.setter
def tasks(self, item):
self._tasks = json.dumps(item)
@property
def module_args(self):
task_tuple = []
for module, args in json.loads(self._modules_args, object_pairs_hook=OrderedDict).items():
task_tuple.append((module, args))
return task_tuple
def hosts(self):
return json.loads(self._hosts)
@module_args.setter
def module_args(self, task_tuple):
module_args_ = OrderedDict({})
for module, args in task_tuple:
module_args_[module] = args
self._modules_args = json.dumps(module_args_)
@hosts.setter
def hosts(self, item):
self._hosts = json.dumps(item)
@property
def short_version(self):
return str(self.version).split('-')[-1]
class History(models.Model):
def __str__(self):
return "{} of {}".format(self.subject.name, self.short_version)
class AdHocHistory(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, primary_key=True)
adhoc = models.ForeignKey(AdHocData, on_delete=models.CASCADE)
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start time'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time'))
timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
......@@ -58,3 +78,10 @@ class History(models.Model):
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
result = models.TextField(blank=True, null=True, verbose_name=_('Playbook raw result'))
summary = models.TextField(blank=True, null=True, verbose_name=_('Playbook summary'))
@property
def short_id(self):
return str(self.id).split('-')[-1]
def __str__(self):
return self.short_id
......@@ -2,12 +2,12 @@
from __future__ import unicode_literals
from rest_framework import serializers
from .models import Playbook
from .models import AdHoc
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Playbook
model = AdHoc
fields = '__all__'
......@@ -88,3 +88,14 @@ def is_uuid(s):
else:
return False
def asset_to_dict(asset):
return asset.to_json()
def asset_to_dict_with_credential(asset):
return asset._to_secret_json()
def system_user_to_dict_with_credential(system_user):
return system_user._to_secret_json()
......@@ -9,13 +9,13 @@ from django.views.generic import ListView, DetailView, View
from django.utils import timezone
from django.shortcuts import redirect, reverse
from .models import Playbook
from .models import AdHoc, AdHocData, AdHocHistory
from ops.tasks import rerun_task
class TaskListView(ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
model = Playbook
model = AdHoc
ordering = ('-date_start',)
context_object_name = 'task_list'
template_name = 'ops/task_list.html'
......@@ -63,7 +63,7 @@ class TaskListView(ListView):
class TaskDetailView(DetailView):
model = Playbook
model = AdHocHistory
template_name = 'ops/task_detail.html'
def get_context_data(self, **kwargs):
......
......@@ -53,79 +53,6 @@ def get_user_group_granted_assets(user_group):
return assets
# def get_user_granted_asset_groups_direct(user):
# """Return asset groups granted of the user direct nor inherit from user group
#
# :param user: Instance of :class: ``User``
# :return: {asset_group: {system_user1, },
# asset_group2: {system_user1, system_user2]}
# """
# asset_groups = {}
# asset_permissions_direct = user.asset_permissions.all()
#
# for asset_permission in asset_permissions_direct:
# if not asset_permission.is_valid:
# continue
# for asset_group in asset_permission.asset_groups.all():
# if asset_group in asset_groups:
# asset_groups[asset_group] |= set(asset_permission.system_users.all())
# else:
# setattr(asset_group, 'inherited', False)
# asset_groups[asset_group] = set(asset_permission.system_users.all())
#
# return asset_groups
# def get_user_granted_asset_groups_inherit_from_user_groups(user):
# """Return asset groups granted of the user and inherit from user group
#
# :param user: Instance of :class: ``User``
# :return: {asset_group: {system_user1, },
# asset_group2: {system_user1, system_user2]}
# """
# asset_groups = {}
# user_groups = user.groups.all()
# asset_permissions = set()
#
# # Get asset permission list of user groups for this user
# for user_group in user_groups:
# asset_permissions |= set(user_group.asset_permissions.all())
#
# # Get asset groups granted from user groups
# for asset_permission in asset_permissions:
# if not asset_permission.is_valid:
# continue
# for asset_group in asset_permission.asset_groups.all():
# if asset_group in asset_groups:
# asset_groups[asset_group] |= set(asset_permission.system_users.all())
# else:
# setattr(asset_group, 'inherited', True)
# asset_groups[asset_group] = set(asset_permission.system_users.all())
#
# return asset_groups
# def get_user_granted_asset_groups(user):
# """Get user granted asset groups all, include direct and inherit from user group
#
# :param user: Instance of :class: ``User``
# :return: {asset_group1: {system_user1, system_user2}, asset_group2: {...}}
# """
#
# asset_groups_inherit_from_user_groups = \
# get_user_granted_asset_groups_inherit_from_user_groups(user)
# asset_groups_direct = get_user_granted_asset_groups_direct(user)
# asset_groups = asset_groups_inherit_from_user_groups
#
# # Merge direct granted and inherit from user group
# for asset_group, system_users in asset_groups_direct.items():
# if asset_group in asset_groups:
# asset_groups[asset_group] |= asset_groups_direct[asset_group]
# else:
# asset_groups[asset_group] = asset_groups_direct[asset_group]
# return asset_groups
def get_user_granted_assets_direct(user):
"""Return assets granted of the user directly
......@@ -225,6 +152,22 @@ def get_user_asset_permissions(user):
return direct_permissions | user_group_permissions
def get_user_granted_system_users(user):
"""
:param user: the user
:return: {"system_user": ["asset", "asset1"], "system_user": []}
"""
assets = get_user_granted_assets(user)
system_users_dict = {}
for asset, system_users in assets.items():
for system_user in system_users:
if system_user in system_users_dict:
system_users_dict[system_user].append(asset)
else:
system_users_dict[system_user] = [asset]
return system_users_dict
def get_user_groups_granted_in_asset(asset):
pass
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment