摘自jumpserver 中 ansible模块 重写runner. ansible2.3版本 pip3 install ansible
目录结构如下,
新建以下 三个文件。 例子在 runner 最后。
├── callback.py
├── __init__.py
├── inventory.py
└── runner.py
callback.py
# ~*~ 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)
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)
else:
self.result_q[n][res._host.name] = [res._result]
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)
def v2_playbook_on_task_start(self, task, is_conditional):
pass
def v2_playbook_on_play_start(self, play):
pass
class PlaybookResultCallBack(CallbackBase):
"""
Custom callback model for handlering the output data of
execute playbook file,
Base on the build-in callback plugins of ansible which named `json`.
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'Dict'
def __init__(self, display=None):
super(PlaybookResultCallBack, self).__init__(display)
self.results = []
self.output = ""
self.item_results = {} # {"host": []}
def _new_play(self, play):
return {
'play': {
'name': play.name,
'id': str(play._uuid)
},
'tasks': []
}
def _new_task(self, task):
return {
'task': {
'name': task.get_name(),
},
'hosts': {}
}
def v2_playbook_on_no_hosts_matched(self):
self.output = "skipping: No match hosts."
def v2_playbook_on_no_hosts_remaining(self):
pass
def v2_playbook_on_task_start(self, task, is_conditional):
self.results[-1]['tasks'].append(self._new_task(task))
def v2_playbook_on_play_start(self, play):
self.results.append(self._new_play(play))
def v2_playbook_on_stats(self, stats):
hosts = sorted(stats.processed.keys())
summary = {}
for h in hosts:
s = stats.summarize(h)
summary[h] = s
if self.output:
pass
else:
self.output = {
'plays': self.results,
'stats': summary
}
def gather_result(self, res):
if res._task.loop and "results" in res._result and res._host.name in self.item_results:
res._result.update({"results": self.item_results[res._host.name]})
del self.item_results[res._host.name]
self.results[-1]['tasks'][-1]['hosts'][res._host.name] = res._result
def v2_runner_on_ok(self, res, **kwargs):
if "ansible_facts" in res._result:
del res._result["ansible_facts"]
self.gather_result(res)
def v2_runner_on_failed(self, res, **kwargs):
self.gather_result(res)
def v2_runner_on_unreachable(self, res, **kwargs):
self.gather_result(res)
def v2_runner_on_skipped(self, res, **kwargs):
self.gather_result(res)
def gather_item_result(self, res):
self.item_results.setdefault(res._host.name, []).append(res._result)
def v2_runner_item_on_ok(self, res):
self.gather_item_result(res)
def v2_runner_item_on_failed(self, res):
self.gather_item_result(res)
def v2_runner_item_on_skipped(self, res):
self.gather_item_result(res)
inventory.py
# ~*~ coding: utf-8 ~*~
from ansible.inventory import Inventory, Host, Group
from ansible.vars import VariableManager
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 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'])
# 添加密码和秘钥
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'])
# 添加become支持
become = asset.get("become", False)
if become:
self.set_variable("ansible_become", True)
self.set_variable("ansible_become_method", become.get('method', 'sudo'))
self.set_variable("ansible_become_user", become.get('user', 'root'))
self.set_variable("ansible_become_pass", become.get('pass', ''))
else:
self.set_variable("ansible_become", False)
class JMSInventory(Inventory):
"""
提供生成Ansible inventory对象的方法
"""
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)
def parse_inventory(self, host_list):
"""用于生成动态构建Ansible Inventory.
self.host_list: [
{"name": "asset_name",
"ip": ,
"port": ,
"user": ,
"pass": ,
"key": ,
"groups": ['group1', 'group2'],
"other_host_var": },
{...},
]
:return: 返回一个Ansible的inventory对象
"""
# 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:
group = Group(group_name)
self.groups[group_name] = group
else:
group = self.groups[group_name]
group.add_host(host)
else:
ungrouped.add_host(host)
all.add_host(host)
runner.py
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
import os
from collections import namedtuple, defaultdict
import sys
sys.path.append('hostinfo/ansible_runner/')
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.vars 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
__all__ = ["AdHocRunner", "PlayBookRunner"]
C.HOST_KEY_CHECKING = False
#logger = get_logger(__name__)
# Jumpserver not use playbook
class PlayBookRunner(object):
"""
用于执行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):
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.variable_manager.extra_vars = load_extra_vars(loader=self.loader,
options=self.options)
self.variable_manager.options_vars = load_options_vars(self.options)
self.variable_manager.set_inventory(self.inventory)
# 初始化playbook的executor
self.runner = 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,
)
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
@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
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,
))
)
self.play_source = dict(
name=task_name,
hosts=pattern,
gather_facts=self.gather_facts,
tasks=self.tasks
)
self.play = Play().load(
self.play_source,
variable_manager=self.variable_manager,
loader=self.loader,
)
self.runner = TaskQueueManager(
inventory=self.inventory,
variable_manager=self.variable_manager,
loader=self.loader,
options=self.options,
passwords=self.passwords,
stdout_callback=self.results_callback,
)
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)
except Exception as e:
logger.warning(e)
else:
#logger.debug(self.results_callback.result_q)
return self.results_callback.result_q
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
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)
task_tuple = (('setup',''),) ##例子,调用setup,获取资产信息
runner = AdHocRunner(assets)
result = runner.run(task_tuple=task_tuple,pattern='all', task_name='Ansible Ad-hoc')
print(result)
#play = PlayBookRunner(assets, playbook_path='/tmp/some.yml') ##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()
if __name__ == "__main__":
test_run()