[Feature] 优化Ops ansible api

pull/828/merge
ibuler 2017-12-06 18:31:51 +08:00
parent ec8106e43d
commit e57121a780
16 changed files with 501 additions and 384 deletions

View File

@ -1,9 +1,10 @@
# ~*~ coding: utf-8 ~*~
#
from ops.utils import run_AdHoc
from .models import Asset
def test_admin_user_connective_manual(asset):
from ops.utils import run_AdHoc
if not isinstance(asset, list):
asset = [asset]
task_tuple = (
@ -15,3 +16,6 @@ def test_admin_user_connective_manual(asset):
else:
return True
def get_assets_by_id_list(id_list):
return Asset.objects.filter(id__in=id_list)

View File

@ -1418,23 +1418,23 @@ msgid "Assets id"
msgstr "资产id"
#: ops/models.py:27
msgid "Task module and args json format"
msgid "Playbook module and args json format"
msgstr ""
#: ops/models.py:28
msgid "Task run pattern"
msgid "Playbook run pattern"
msgstr ""
#: ops/models.py:29
msgid "Task raw result"
msgid "Playbook raw result"
msgstr ""
#: ops/models.py:30
msgid "Task summary"
msgid "Playbook summary"
msgstr ""
#: ops/templates/ops/task_detail.html:19
msgid "Task replay detail"
msgid "Playbook replay detail"
msgstr "任务记录详情"
#: ops/templates/ops/task_detail.html:62
@ -1669,7 +1669,7 @@ msgid "Job Center"
msgstr "作业中心"
#: templates/_nav.html:51
msgid "Task"
msgid "Playbook"
msgstr "任务"
#: templates/_nav.html:62

View File

@ -1,65 +1,76 @@
# ~*~ coding: utf-8 ~*~
from collections import defaultdict
from ansible.plugins.callback import CallbackBase
class CommandResultCallback(CallbackBase):
def __init__(self, display=None):
self.result_q = dict(contacted={}, dark={})
super(CommandResultCallback, self).__init__(display)
def gather_result(self, n, res):
self.result_q[n][res._host.name] = {}
self.result_q[n][res._host.name]['cmd'] = res._result.get('cmd')
self.result_q[n][res._host.name]['stderr'] = res._result.get('stderr')
self.result_q[n][res._host.name]['stdout'] = res._result.get('stdout')
self.result_q[n][res._host.name]['rc'] = res._result.get('rc')
def v2_runner_on_ok(self, result):
self.gather_result("contacted", result)
def v2_runner_on_failed(self, result, ignore_errors=False):
self.gather_result("dark", result)
def v2_runner_on_unreachable(self, result):
self.gather_result("dark", result)
def v2_runner_on_skipped(self, result):
self.gather_result("dark", result)
class AdHocResultCallback(CallbackBase):
"""
AdHoc result Callback
"""
def __init__(self, display=None):
self.result_q = dict(contacted={}, dark={})
super(AdHocResultCallback, self).__init__(display)
# result_raw example: {
# "ok": {"hostname": []},
# "failed": {"hostname": []},
# "unreachable: {"hostname": []},
# "skipped": {"hostname": []},
# }
# results_summary example: {
# "contacted": {"hostname",...},
# "dark": {"hostname": ["error",...],},
# }
self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={})
self.results_summary = dict(contacted=set(), dark={})
super().__init__(display)
def gather_result(self, n, res):
if res._host.name in self.result_q[n]:
self.result_q[n][res._host.name].append(res._result)
def gather_result(self, t, host, res):
if self.results_raw[t].get(host):
self.results_raw[t][host].append(res)
else:
self.result_q[n][res._host.name] = [res._result]
self.results_raw[t][host] = [res]
self.clean_result(t, host, res)
def v2_runner_on_ok(self, result):
self.gather_result("contacted", result)
def clean_result(self, t, host, res):
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 host in contacted:
contacted.remove(dark)
def v2_runner_on_failed(self, result, ignore_errors=False):
self.gather_result("dark", result)
def runner_on_ok(self, host, res):
self.gather_result("ok", host, res)
def v2_runner_on_unreachable(self, result):
self.gather_result("dark", result)
def runner_on_failed(self, host, res, ignore_errors=False):
self.gather_result("failed", host, res)
def v2_runner_on_skipped(self, result):
self.gather_result("dark", result)
def runner_on_unreachable(self, host, res):
self.gather_result("unreachable", host, res)
def v2_playbook_on_task_start(self, task, is_conditional):
pass
def runner_on_skipped(self, host, item=None):
self.gather_result("skipped", host, item)
def v2_playbook_on_play_start(self, play):
pass
class CommandResultCallback(AdHocResultCallback):
def __init__(self, display=None):
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_cmd(self, t, host, res):
cmd = {}
if t == "ok":
cmd['cmd'] = res.get('cmd')
cmd['stderr'] = res.get('stderr')
cmd['stdout'] = res.get('stdout')
cmd['rc'] = res.get('rc')
else:
cmd['err'] = "Error: {}".format(res)
self.results_command[host] = cmd
class PlaybookResultCallBack(CallbackBase):

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
#
class AnsibleError(Exception):
pass

View File

@ -1,31 +1,52 @@
# ~*~ coding: utf-8 ~*~
from ansible.inventory import Inventory, Host, Group
from ansible.vars import VariableManager
from ansible.inventory.group import Group
from ansible.inventory.host import Host
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.parsing.dataloader import DataLoader
class JMSHost(Host):
def __init__(self, asset):
self.asset = asset
self.name = name = asset.get('hostname') or asset.get('ip')
self.port = port = asset.get('port') or 22
super(JMSHost, self).__init__(name, port)
self.set_all_variable()
def __init__(self, host_data):
"""
初始化
:param host_data: {
"hostname": "",
"ip": "",
"port": "",
"username": "",
"password": "",
"private_key": "",
"become": {
"method": "",
"user": "",
"pass": "",
}
"groups": [],
"vars": {},
}
"""
self.host_data = host_data
hostname = host_data.get('hostname') or host_data.get('ip')
port = host_data.get('port') or 22
super(JMSHost, self).__init__(hostname, port)
self.__set_required_variables()
self.__set_extra_variables()
def set_all_variable(self):
asset = self.asset
self.set_variable('ansible_host', asset['ip'])
self.set_variable('ansible_port', asset['port'])
self.set_variable('ansible_user', asset['username'])
def __set_required_variables(self):
host_data = self.host_data
self.set_variable('ansible_host', host_data['ip'])
self.set_variable('ansible_port', host_data['port'])
self.set_variable('ansible_user', host_data['username'])
# 添加密码和秘钥
if asset.get('password'):
self.set_variable('ansible_ssh_pass', asset['password'])
if asset.get('private_key'):
self.set_variable('ansible_ssh_private_key_file', asset['private_key'])
if host_data.get('password'):
self.set_variable('ansible_ssh_pass', host_data['password'])
if host_data.get('private_key'):
self.set_variable('ansible_ssh_private_key_file', host_data['private_key'])
# 添加become支持
become = asset.get("become", False)
become = host_data.get("become", False)
if become:
self.set_variable("ansible_become", True)
self.set_variable("ansible_become_method", become.get('method', 'sudo'))
@ -34,58 +55,73 @@ class JMSHost(Host):
else:
self.set_variable("ansible_become", False)
def __set_extra_variables(self):
for k, v in self.host_data.get('vars', {}).items():
self.set_variable(k, v)
class JMSInventory(Inventory):
def __repr__(self):
return self.name
class JMSInventory(InventoryManager):
"""
提供生成Ansible inventory对象的方法
"""
loader_class = DataLoader
variable_manager_class = VariableManager
host_manager_class = JMSHost
def __init__(self, host_list=None):
if host_list is None:
host_list = []
assert isinstance(host_list, list)
self.host_list = host_list
self.loader = DataLoader()
self.variable_manager = VariableManager()
super(JMSInventory, self).__init__(self.loader, self.variable_manager,
host_list=host_list)
assert isinstance(host_list, list)
self.loader = self.loader_class()
self.variable_manager = self.variable_manager_class()
super().__init__(self.loader)
def parse_inventory(self, host_list):
"""用于生成动态构建Ansible Inventory.
self.host_list: [
{"name": "asset_name",
"ip": <ip>,
"port": <port>,
"user": <user>,
"pass": <pass>,
"key": <sshKey>,
"groups": ['group1', 'group2'],
"other_host_var": <other>},
{...},
def get_groups(self):
return self._inventory.groups
def get_group(self, name):
return self._inventory.groups.get(name, None)
def parse_sources(self, cache=False):
"""
用于生成动态构建Ansible Inventory. super().__init__ 会自动调用
host_list: [{
"hostname": "",
"ip": "",
"port": "",
"username": "",
"password": "",
"private_key": "",
"become": {
"method": "",
"user": "",
"pass": "",
},
"groups": [],
"vars": {},
},
]
:return: 返回一个Ansible的inventory对象
:return: None
"""
group_all = self.get_group('all')
ungrouped = self.get_group('ungrouped')
# TODO: 验证输入
# 创建Ansible Group,如果没有则创建default组
ungrouped = Group('ungrouped')
all = Group('all')
all.add_child_group(ungrouped)
self.groups = dict(all=all, ungrouped=ungrouped)
for asset in host_list:
host = JMSHost(asset=asset)
asset_groups = asset.get('groups')
if asset_groups:
for group_name in asset_groups:
if group_name not in self.groups:
for host_data in self.host_list:
host = self.host_manager_class(host_data=host_data)
self.hosts[host_data['hostname']] = host
groups_data = host_data.get('groups')
if groups_data:
for group_name in groups_data:
group = self.get_group(group_name)
if group is None:
group = Group(group_name)
self.groups[group_name] = group
else:
group = self.groups[group_name]
self.add_group(group)
group.add_host(host)
else:
ungrouped.add_host(host)
all.add_host(host)
group_all.add_host(host)

View File

@ -2,303 +2,233 @@
from __future__ import unicode_literals
import os
from collections import namedtuple, defaultdict
from collections import namedtuple
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.vars import VariableManager
from ansible.vars.manager import VariableManager
from ansible.parsing.dataloader import DataLoader
from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.playbook.play import Play
import ansible.constants as C
from ansible.utils.vars import load_extra_vars
from ansible.utils.vars import load_options_vars
from .inventory import JMSInventory
from .callback import AdHocResultCallback, PlaybookResultCallBack, \
CommandResultCallback
from common.utils import get_logger
from .exceptions import AnsibleError
__all__ = ["AdHocRunner", "PlayBookRunner"]
C.HOST_KEY_CHECKING = False
logger = get_logger(__name__)
Options = namedtuple('Options', [
'listtags', 'listtasks', 'listhosts', 'syntax', 'connection',
'module_path', 'forks', 'remote_user', 'private_key_file', 'timeout',
'ssh_common_args', 'ssh_extra_args', 'sftp_extra_args',
'scp_extra_args', 'become', 'become_method', 'become_user',
'verbosity', 'check', 'extra_vars', 'playbook_path', 'passwords',
'diff',
])
def get_default_options():
options = Options(
listtags=False,
listtasks=False,
listhosts=False,
syntax=False,
timeout=60,
connection='ssh',
module_path='',
forks=10,
remote_user='root',
private_key_file=None,
ssh_common_args="",
ssh_extra_args="",
sftp_extra_args="",
scp_extra_args="",
become=None,
become_method=None,
become_user=None,
verbosity=None,
extra_vars=[],
check=False,
playbook_path='/etc/ansible/',
passwords=None,
diff=False,
)
return options
# Jumpserver not use playbook
class PlayBookRunner(object):
class PlayBookRunner:
"""
用于执行AnsiblePlaybook的接口.简化Playbook对象的使用.
"""
Options = namedtuple('Options', [
'listtags', 'listtasks', 'listhosts', 'syntax', 'connection',
'module_path', 'forks', 'remote_user', 'private_key_file', 'timeout',
'ssh_common_args', 'ssh_extra_args', 'sftp_extra_args',
'scp_extra_args', 'become', 'become_method', 'become_user',
'verbosity', 'check', 'extra_vars'])
def __init__(self,
hosts=None,
playbook_path=None,
forks=C.DEFAULT_FORKS,
listtags=False,
listtasks=False,
listhosts=False,
syntax=False,
module_path=None,
remote_user='root',
timeout=C.DEFAULT_TIMEOUT,
ssh_common_args=None,
ssh_extra_args=None,
sftp_extra_args=None,
scp_extra_args=None,
become=True,
become_method=None,
become_user="root",
verbosity=None,
extra_vars=None,
connection_type="ssh",
passwords=None,
private_key_file=None,
check=False):
# Default results callback
results_callback_class = PlaybookResultCallBack
inventory_class = JMSInventory
loader_class = DataLoader
variable_manager_class = VariableManager
options = get_default_options()
def __init__(self, hosts=None, options=None):
"""
:param options: Ansible options like ansible.cfg
:param hosts: [
{
"hostname": "",
"ip": "",
"port": "",
"username": "",
"password": "",
"private_key": "",
"become": {
"method": "",
"user": "",
"pass": "",
},
"groups": [],
"vars": {},
},
]
"""
if options:
self.options = options
C.RETRY_FILES_ENABLED = False
self.callbackmodule = PlaybookResultCallBack()
if playbook_path is None or not os.path.exists(playbook_path):
raise AnsibleError(
"Not Found the playbook file: %s." % playbook_path)
self.playbook_path = playbook_path
self.loader = DataLoader()
self.variable_manager = VariableManager()
self.passwords = passwords or {}
self.inventory = JMSInventory(hosts)
self.options = self.Options(
listtags=listtags,
listtasks=listtasks,
listhosts=listhosts,
syntax=syntax,
timeout=timeout,
connection=connection_type,
module_path=module_path,
forks=forks,
remote_user=remote_user,
private_key_file=private_key_file,
ssh_common_args=ssh_common_args or "",
ssh_extra_args=ssh_extra_args or "",
sftp_extra_args=sftp_extra_args,
scp_extra_args=scp_extra_args,
become=become,
become_method=become_method,
become_user=become_user,
verbosity=verbosity,
extra_vars=extra_vars or [],
check=check
self.inventory = self.inventory_class(hosts)
self.loader = self.loader_class()
self.results_callback = self.results_callback_class()
self.playbook_path = options.playbook_path
self.variable_manager = self.variable_manager_class(
loader=self.loader, inventory=self.inventory
)
self.passwords = options.passwords
self.__check()
self.variable_manager.extra_vars = load_extra_vars(loader=self.loader,
options=self.options)
self.variable_manager.options_vars = load_options_vars(self.options)
def __check(self):
if self.options.playbook_path is None or \
not os.path.exists(self.options.playbook_path):
raise AnsibleError(
"Not Found the playbook file: {}.".format(self.options.playbook_path)
)
if not self.inventory.list_hosts('all'):
raise AnsibleError('Inventory is empty')
self.variable_manager.set_inventory(self.inventory)
# 初始化playbook的executor
self.runner = PlaybookExecutor(
def run(self):
executor = PlaybookExecutor(
playbooks=[self.playbook_path],
inventory=self.inventory,
variable_manager=self.variable_manager,
loader=self.loader,
options=self.options,
passwords=self.passwords)
if self.runner._tqm:
self.runner._tqm._stdout_callback = self.callbackmodule
def run(self):
if not self.inventory.list_hosts('all'):
raise AnsibleError('Inventory is empty')
self.runner.run()
self.runner._tqm.cleanup()
return self.callbackmodule.output
class AdHocRunner(object):
"""
ADHoc接口
"""
Options = namedtuple("Options", [
'connection', 'module_path', 'private_key_file', "remote_user",
'timeout', 'forks', 'become', 'become_method', 'become_user',
'check', 'extra_vars',
]
)
results_callback_class = AdHocResultCallback
def __init__(self,
hosts=C.DEFAULT_HOST_LIST,
forks=C.DEFAULT_FORKS, # 5
timeout=C.DEFAULT_TIMEOUT, # SSH timeout = 10s
remote_user=C.DEFAULT_REMOTE_USER, # root
module_path=None, # dirs of custome modules
connection_type="smart",
become=None,
become_method=None,
become_user=None,
check=False,
passwords=None,
extra_vars=None,
private_key_file=None,
gather_facts='no'):
self.pattern = ''
self.variable_manager = VariableManager()
self.loader = DataLoader()
self.gather_facts = gather_facts
self.results_callback = AdHocRunner.results_callback_class()
self.options = self.Options(
connection=connection_type,
timeout=timeout,
module_path=module_path,
forks=forks,
become=become,
become_method=become_method,
become_user=become_user,
check=check,
remote_user=remote_user,
extra_vars=extra_vars or [],
private_key_file=private_key_file,
passwords=self.passwords
)
self.variable_manager.extra_vars = load_extra_vars(self.loader,
options=self.options)
self.variable_manager.options_vars = load_options_vars(self.options)
self.passwords = passwords or {}
self.inventory = JMSInventory(hosts)
self.variable_manager.set_inventory(self.inventory)
self.tasks = []
self.play_source = None
self.play = None
self.runner = None
if executor._tqm:
executor._tqm._stdout_callback = self.results_callback
executor.run()
executor._tqm.cleanup()
return self.results_callback.output
class AdHocRunner:
"""
ADHoc Runner接口
"""
results_callback_class = AdHocResultCallback
inventory_class = JMSInventory
loader_class = DataLoader
variable_manager_class = VariableManager
options = get_default_options()
default_options = get_default_options()
def __init__(self, hosts, options=None):
if options:
self.options = options
self.pattern = ''
self.loader = DataLoader()
self.inventory = self.inventory_class(hosts)
self.variable_manager = VariableManager(loader=self.loader, inventory=self.inventory)
@staticmethod
def check_module_args(module_name, module_args=''):
if module_name in C.MODULE_REQUIRE_ARGS and not module_args:
err = "No argument passed to '%s' module." % module_name
print(err)
return False
return True
raise AnsibleError(err)
def run(self, task_tuple, pattern='all', task_name='Ansible Ad-hoc'):
"""
:param task_tuple: (('shell', 'ls'), ('ping', ''))
:param pattern:
:param task_name:
:return:
"""
for module, args in task_tuple:
if not self.check_module_args(module, args):
return
self.tasks.append(
dict(action=dict(
module=module,
args=args,
))
def check_pattern(self, pattern):
if not self.inventory.list_hosts("all"):
raise AnsibleError("Inventory is empty.")
if not self.inventory.list_hosts(pattern):
raise AnsibleError(
"pattern: %s dose not match any hosts." % pattern
)
self.play_source = dict(
name=task_name,
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
"""
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]
:param pattern: all, *, or others
:param play_name: The play name
:return:
"""
results_callback = self.results_callback_class()
clean_tasks = []
for task in tasks:
self.check_module_args(task['action']['module'], task['action'].get('args'))
clean_tasks.append(task)
play_source = dict(
name=play_name,
hosts=pattern,
gather_facts=self.gather_facts,
tasks=self.tasks
gather_facts=gather_facts,
tasks=clean_tasks
)
self.play = Play().load(
self.play_source,
play = Play().load(
play_source,
variable_manager=self.variable_manager,
loader=self.loader,
)
self.runner = TaskQueueManager(
tqm = TaskQueueManager(
inventory=self.inventory,
variable_manager=self.variable_manager,
loader=self.loader,
options=self.options,
passwords=self.passwords,
stdout_callback=self.results_callback,
stdout_callback=results_callback,
passwords=self.options.passwords,
)
if not self.inventory.list_hosts("all"):
raise AnsibleError("Inventory is empty.")
if not self.inventory.list_hosts(self.pattern):
raise AnsibleError(
"pattern: %s dose not match any hosts." % self.pattern)
try:
self.runner.run(self.play)
tqm.run(play)
return results_callback
except Exception as e:
logger.warning(e)
else:
logger.debug(self.results_callback.result_q)
return self.results_callback.result_q
raise AnsibleError(e)
finally:
if self.runner:
self.runner.cleanup()
if self.loader:
self.loader.cleanup_all_tmp_files()
def clean_result(self):
"""
:return: {
"success": ['hostname',],
"failed": [('hostname', 'msg'), {}],
}
"""
result = {'success': [], 'failed': []}
for host in self.results_callback.result_q['contacted']:
result['success'].append(host)
for host, msgs in self.results_callback.result_q['dark'].items():
msg = '\n'.join(['{} {}: {}'.format(
msg.get('module_stdout', ''),
msg.get('invocation', {}).get('module_name'),
msg.get('msg', '')) for msg in msgs])
result['failed'].append((host, msg))
return result
tqm.cleanup()
self.loader.cleanup_all_tmp_files()
def test_run():
assets = [
{
"hostname": "192.168.244.129",
"ip": "192.168.244.129",
"port": 22,
"username": "root",
"password": "redhat",
},
]
task_tuple = (('shell', 'ls'),)
hoc = AdHocRunner(hosts=assets)
hoc.results_callback = CommandResultCallback()
ret = hoc.run(task_tuple)
print(ret)
class CommandRunner(AdHocRunner):
results_callback_class = CommandResultCallback
modules_choices = ('shell', 'raw', 'command', 'script')
#play = PlayBookRunner(assets, playbook_path='/tmp/some.yml')
"""
# /tmp/some.yml
---
- name: Test the plabybook API.
hosts: all
remote_user: root
gather_facts: yes
tasks:
- name: exec uptime
shell: uptime
"""
#play.run()
def execute(self, cmd, pattern, module=None):
if module and module not in self.modules_choices:
raise AnsibleError("Module should in {}".format(self.modules_choices))
else:
module = "shell"
tasks = [
{"action": {"module": module, "args": cmd}}
]
hosts = self.inventory.get_hosts(pattern=pattern)
name = "Run command {} on {}".format(cmd, ", ".join([host.name for host in hosts]))
return self.run(tasks, pattern, play_name=name)
if __name__ == "__main__":
test_run()

View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
#
import sys
import unittest
sys.path.insert(0, '../..')
from ops.ansible.inventory import JMSInventory
class TestJMSInventory(unittest.TestCase):
def setUp(self):
host_list = [{
"hostname": "testserver1",
"ip": "102.1.1.1",
"port": 22,
"username": "root",
"password": "password",
"private_key": "/tmp/private_key",
"become": {
"method": "sudo",
"user": "root",
"pass": None,
},
"groups": ["group1", "group2"],
"vars": {"sexy": "yes"},
}, {
"hostname": "testserver2",
"ip": "8.8.8.8",
"port": 2222,
"username": "root",
"password": "password",
"private_key": "/tmp/private_key",
"become": {
"method": "su",
"user": "root",
"pass": "123",
},
"groups": ["group3", "group4"],
"vars": {"love": "yes"},
}]
self.inventory = JMSInventory(host_list=host_list)
def test_hosts(self):
print("#"*10 + "Hosts" + "#"*10)
for host in self.inventory.hosts:
print(host)
def test_groups(self):
print("#" * 10 + "Groups" + "#" * 10)
for group in self.inventory.groups:
print(group)
def test_group_all(self):
print("#" * 10 + "all group hosts" + "#" * 10)
group = self.inventory.get_group('all')
print(group.hosts)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
#
import unittest
import sys
sys.path.insert(0, "../..")
from ops.ansible.runner import AdHocRunner, CommandRunner
class TestAdHocRunner(unittest.TestCase):
def setUp(self):
host_data = [
{
"hostname": "testserver",
"ip": "192.168.244.168",
"port": 22,
"username": "root",
"password": "redhat",
},
]
self.runner = AdHocRunner(hosts=host_data)
def test_run(self):
tasks = [
{"action": {"module": "shell", "args": "ls"}},
{"action": {"module": "shell", "args": "whoami"}},
]
ret = self.runner.run(tasks, "all")
print(ret.results_summary)
print(ret.results_raw)
class TestCommandRunner(unittest.TestCase):
def setUp(self):
host_data = [
{
"hostname": "testserver",
"ip": "192.168.244.168",
"port": 22,
"username": "root",
"password": "redhat",
},
]
self.runner = CommandRunner(hosts=host_data)
def test_execute(self):
res = self.runner.execute('ls', 'all')
print(res.results_command)
if __name__ == "__main__":
unittest.main()

View File

@ -4,12 +4,12 @@
from rest_framework import viewsets
from .hands import IsSuperUser
from .models import Task
from .models import Playbook
from .serializers import TaskSerializer
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
queryset = Playbook.objects.all()
serializer_class = TaskSerializer
permission_classes = (IsSuperUser,)

View File

@ -7,40 +7,32 @@ import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from assets.models import Asset
__all__ = ["Task"]
__all__ = ["Playbook"]
logger = logging.getLogger(__name__)
class Task(models.Model):
class AdHoc(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, 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'))
timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
assets = models.TextField(blank=True, null=True, verbose_name=_('Assets id')) # Asset inventory may be change
_modules_args = models.TextField(blank=True, null=True, verbose_name=_('Task module and args json format'))
pattern = models.CharField(max_length=64, default='all', verbose_name=_('Task run pattern'))
result = models.TextField(blank=True, null=True, verbose_name=_('Task raw result'))
summary = models.TextField(blank=True, null=True, verbose_name=_('Task summary'))
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'))
def __unicode__(self):
return "%s" % self.uuid
def __str__(self):
return "%s" % self.name
@property
def total_assets(self):
assets_id = [i for i in self.assets.split(',') if i.isdigit()]
assets = Asset.objects.filter(id__in=assets_id)
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
@property
def assets_json(self):
return [asset._to_secret_json() for asset in self.total_assets]
def inventory(self):
return [asset._to_secret_json() for asset in self.get_hosts_mapped_assets()]
@property
def module_args(self):
@ -57,3 +49,12 @@ class Task(models.Model):
self._modules_args = json.dumps(module_args_)
class History(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, primary_key=True)
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start time'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time'))
timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
result = models.TextField(blank=True, null=True, verbose_name=_('Playbook raw result'))
summary = models.TextField(blank=True, null=True, verbose_name=_('Playbook summary'))

View File

@ -2,12 +2,12 @@
from __future__ import unicode_literals
from rest_framework import serializers
from .models import Task
from .models import Playbook
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
model = Playbook
fields = '__all__'

View File

@ -12,8 +12,8 @@ logger = get_logger(__file__)
@shared_task
def rerun_task(task_id):
from .models import Task
record = Task.objects.get(uuid=task_id)
from .models import Playbook
record = Playbook.objects.get(uuid=task_id)
assets = record.assets_json
task_tuple = record.module_args
pattern = record.pattern

View File

@ -3,6 +3,7 @@
from __future__ import absolute_import, unicode_literals
import json
import re
import time
import uuid
@ -41,16 +42,16 @@ def run_AdHoc(task_tuple, assets,
runner = AdHocRunner(assets)
if record:
from .models import Task
if not Task.objects.filter(uuid=task_id):
record = Task(uuid=task_id,
name=task_name,
assets=','.join(str(asset['id']) for asset in assets),
module_args=task_tuple,
pattern=pattern)
from .models import Playbook
if not Playbook.objects.filter(uuid=task_id):
record = Playbook(uuid=task_id,
name=task_name,
assets=','.join(str(asset['id']) for asset in assets),
module_args=task_tuple,
pattern=pattern)
record.save()
else:
record = Task.objects.get(uuid=task_id)
record = Playbook.objects.get(uuid=task_id)
record.date_start = timezone.now()
record.date_finished = None
record.timedelta = None
@ -76,3 +77,14 @@ def run_AdHoc(task_tuple, assets,
record.is_success = False
record.save()
return summary, result
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
def is_uuid(s):
if UUID_PATTERN.match(s):
return True
else:
return False

View File

@ -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 Task
from .models import Playbook
from ops.tasks import rerun_task
class TaskListView(ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
model = Task
model = Playbook
ordering = ('-date_start',)
context_object_name = 'task_list'
template_name = 'ops/task_list.html'
@ -53,7 +53,7 @@ class TaskListView(ListView):
def get_context_data(self, **kwargs):
context = {
'app': 'Ops',
'action': 'Task record list',
'action': 'Playbook record list',
'date_from': self.date_from_s,
'date_to': self.date_to_s,
'keyword': self.keyword,
@ -63,13 +63,13 @@ class TaskListView(ListView):
class TaskDetailView(DetailView):
model = Task
model = Playbook
template_name = 'ops/task_detail.html'
def get_context_data(self, **kwargs):
context = {
'app': 'Ops',
'action': 'Task record detail',
'action': 'Playbook record detail',
'results': json.loads(self.object.summary or '{}'),
}
kwargs.update(context)

View File

@ -52,7 +52,7 @@
<i class="fa fa-coffee"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task' %}</a></li>
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Playbook' %}</a></li>
</ul>
</li>

View File

@ -113,7 +113,7 @@ class Task(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, choices=NAME_CHOICES, verbose_name=_("Name"))
args = models.CharField(max_length=1024, verbose_name=_("Task Args"))
args = models.CharField(max_length=1024, verbose_name=_("Playbook Args"))
terminal = models.ForeignKey(Terminal, null=True, on_delete=models.CASCADE)
is_finished = models.BooleanField(default=False)
date_created = models.DateTimeField(auto_now_add=True)