From 8e0fb419739fb70d2b390d9ef53b594d91a7c128 Mon Sep 17 00:00:00 2001 From: pengtao Date: Wed, 23 Jun 2021 14:49:35 +0800 Subject: [PATCH] 1 --- .vscode/launch.json | 15 ++ 1.list | 1 + scripts/common/ansible/__init__.py | 0 scripts/common/ansible/ansible2.9.5.py | 194 ++++++++++++++++++++ scripts/common/ansible/ansible4.py | 137 ++++++++++++++ scripts/common/ansible/mylog.py | 198 +++++++++++++++++++++ scripts/common/ansible/run_playbook.py | 23 +++ scripts/common/ansible/task.py | 159 +++++++++++++++++ scripts/common/myansible.py | 235 +++++++++++++++++++++++++ 9 files changed, 962 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 1.list create mode 100644 scripts/common/ansible/__init__.py create mode 100644 scripts/common/ansible/ansible2.9.5.py create mode 100644 scripts/common/ansible/ansible4.py create mode 100644 scripts/common/ansible/mylog.py create mode 100644 scripts/common/ansible/run_playbook.py create mode 100644 scripts/common/ansible/task.py create mode 100644 scripts/common/myansible.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b80908c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: 当前文件", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/1.list b/1.list new file mode 100644 index 0000000..df1f423 --- /dev/null +++ b/1.list @@ -0,0 +1 @@ +ansible==2.6.2 diff --git a/scripts/common/ansible/__init__.py b/scripts/common/ansible/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/common/ansible/ansible2.9.5.py b/scripts/common/ansible/ansible2.9.5.py new file mode 100644 index 0000000..c312bc3 --- /dev/null +++ b/scripts/common/ansible/ansible2.9.5.py @@ -0,0 +1,194 @@ +import sys +import os +import json +import shutil +from ansible.module_utils.common.collections import ImmutableDict +from ansible.parsing.dataloader import DataLoader +from ansible.vars.manager import VariableManager +from ansible.inventory.manager import InventoryManager +from ansible.playbook.play import Play +from ansible.executor.task_queue_manager import TaskQueueManager +from ansible.plugins.callback import CallbackBase +from ansible import context +import ansible.constants as C + + +class ResultCallback(CallbackBase): + """ + 重写callbackBase类的部分方法 + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.host_ok = {} + self.host_unreachable = {} + self.host_failed = {} + self.task_ok = {} + + def v2_runner_on_unreachable(self, result): + self.host_unreachable[result._host.get_name()] = result + + def v2_runner_on_ok(self, result, **kwargs): + self.host_ok[result._host.get_name()] = result + + def v2_runner_on_failed(self, result, **kwargs): + self.host_failed[result._host.get_name()] = result + + +class MyAnsiable2(): + def __init__(self, + connection='local', # 连接方式 local 本地方式,smart ssh方式 + remote_user=None, # ssh 用户 + remote_password=None, # ssh 用户的密码,应该是一个字典, key 必须是 conn_pass + private_key_file=None, # 指定自定义的私钥地址 + sudo=None, sudo_user=None, ask_sudo_pass=None, + module_path=None, # 模块路径,可以指定一个自定义模块的路径 + become=None, # 是否提权 + become_method=None, # 提权方式 默认 sudo 可以是 su + become_user=None, # 提权后,要成为的用户,并非登录用户 + check=False, diff=False, + listhosts=None, listtasks=None, listtags=None, + verbosity=3, + syntax=None, + start_at_task=None, + inventory=None): + + # 函数文档注释 + """ + 初始化函数,定义的默认的选项值, + 在初始化的时候可以传参,以便覆盖默认选项的值 + """ + context.CLIARGS = ImmutableDict( + connection=connection, + remote_user=remote_user, + private_key_file=private_key_file, + sudo=sudo, + sudo_user=sudo_user, + ask_sudo_pass=ask_sudo_pass, + module_path=module_path, + become=become, + become_method=become_method, + become_user=become_user, + verbosity=verbosity, + listhosts=listhosts, + listtasks=listtasks, + listtags=listtags, + syntax=syntax, + start_at_task=start_at_task, + ) + + # 三元表达式,假如没有传递 inventory, 就使用 "localhost," + # 指定 inventory 文件 + # inventory 的值可以是一个 资产清单文件 + # 也可以是一个包含主机的元组,这个仅仅适用于测试 + # 比如 : 1.1.1.1, # 如果只有一个 IP 最后必须有英文的逗号 + # 或者: 1.1.1.1, 2.2.2.2 + + self.inventory = inventory if inventory else "localhost," + + # 实例化数据解析器 + self.loader = DataLoader() + + # 实例化 资产配置对象 + self.inv_obj = InventoryManager( + loader=self.loader, sources=self.inventory) + + # 设置密码 + self.passwords = remote_password + + # 实例化回调插件对象 + self.results_callback = ResultCallback() + + # 变量管理器 + self.variable_manager = VariableManager(self.loader, self.inv_obj) + + def run(self, hosts='localhost', gether_facts="no", module="ping", args='', task_time=0): + """ + 参数说明: + task_time -- 执行异步任务时等待的秒数,这个需要大于 0 ,等于 0 的时候不支持异步(默认值)。这个值应该等于执行任务实际耗时时间为好 + """ + play_source = dict( + name="Ad-hoc", + hosts=hosts, + gather_facts=gether_facts, + tasks=[ + # 这里每个 task 就是这个列表中的一个元素,格式是嵌套的字典 + # 也可以作为参数传递过来,这里就简单化了。 + {"action": {"module": module, "args": args}, "async": task_time, "poll": 0}]) + + play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader) + + tqm = None + try: + tqm = TaskQueueManager( + inventory=self.inv_obj, + variable_manager=self.variable_manager, + loader=self.loader, + passwords=self.passwords, + stdout_callback=self.results_callback) + + result = tqm.run(play) + + finally: + if tqm is not None: + tqm.cleanup() + shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) + + def playbook(self, playbooks, run_data): + """ + Keyword arguments: + playbooks -- 需要是一个列表类型 + """ + from ansible.executor.playbook_executor import PlaybookExecutor + self.variable_manager.extra_vars = run_data + playbook = PlaybookExecutor(playbooks=playbooks, + inventory=self.inv_obj, + variable_manager=self.variable_manager, + loader=self.loader, + passwords=self.passwords) + + # 使用回调函数 + playbook._tqm._stdout_callback = self.results_callback + + result = playbook.run() + + def get_result(self): + result_raw = {'success': {}, 'failed': {}, 'unreachable': {}} + + # print(self.results_callback.host_ok) + for host, result in self.results_callback.host_ok.items(): + result_raw['success'][host] = result._result + for host, result in self.results_callback.host_failed.items(): + result_raw['failed'][host] = result._result + for host, result in self.results_callback.host_unreachable.items(): + result_raw['unreachable'][host] = result._result + + # 最终打印结果,并且使用 JSON 继续格式化 + print(json.dumps(result_raw, indent=4)) + + return json.dumps(result_raw) + + +def test_run_cmd(): + hosts = "192.168.100.42" + ansible3 = MyAnsiable2( + inventory='hosts', connection='smart') # 创建资源库对象 + ansible3.run(hosts=hosts, module="shell", + args=' ps -ef |grep python') + stdout_dict = json.loads(ansible3.get_result()) + print(stdout_dict, type(stdout_dict)) + print(stdout_dict['success'][hosts]['stdout'], '######wc') + + +def test_run_playbook(): + ansible3 = MyAnsiable2( + inventory='hosts', connection='smart') + run_data = { + "dest_paths": "/data/apps/temp", + "source": "/root/temp/host", + "dest_filename": "/data/apps/temp/host.new" + } + ansible3.playbook(playbooks=['test.yml']) + stdout_dict = json.loads(ansible3.get_result()) + print(stdout_dict, type(stdout_dict)) + # print(stdout_dict['success']['192.168.0.94']['stdout']) diff --git a/scripts/common/ansible/ansible4.py b/scripts/common/ansible/ansible4.py new file mode 100644 index 0000000..e346f10 --- /dev/null +++ b/scripts/common/ansible/ansible4.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# pip3 install ansible +# pip3 install ansible_runner +# pip3 install ansible_inventory +# pip3 install ansible_playbook +# Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12 +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import shutil + +import ansible.constants as C +from ansible.executor.task_queue_manager import TaskQueueManager +from ansible.module_utils.common.collections import ImmutableDict +from ansible.inventory.manager import InventoryManager +from ansible.parsing.dataloader import DataLoader +from ansible.playbook.play import Play +from ansible.plugins.callback import CallbackBase +from ansible.vars.manager import VariableManager +from ansible import context + + +# Create a callback plugin so we can capture the output +class ResultsCollectorJSONCallback(CallbackBase): + """A sample callback plugin used for performing an action as results come in. + + If you want to collect all results into a single object for processing at + the end of the execution, look into utilizing the ``json`` callback plugin + or writing your own custom callback plugin. + """ + + def __init__(self, *args, **kwargs): + super(ResultsCollectorJSONCallback, self).__init__(*args, **kwargs) + self.host_ok = {} + self.host_unreachable = {} + self.host_failed = {} + + def v2_runner_on_unreachable(self, result): + host = result._host + self.host_unreachable[host.get_name()] = result + + def v2_runner_on_ok(self, result, *args, **kwargs): + """Print a json representation of the result. + + Also, store the result in an instance attribute for retrieval later + """ + host = result._host + self.host_ok[host.get_name()] = result + print(json.dumps({host.name: result._result}, indent=4)) + + def v2_runner_on_failed(self, result, *args, **kwargs): + host = result._host + self.host_failed[host.get_name()] = result + + +def main(): + host_list = ['localhost', 'www.example.com', 'www.google.com'] + # since the API is constructed for CLI it expects certain options to always be set in the context object + context.CLIARGS = ImmutableDict(connection='smart', module_path=['/to/mymodules', '/usr/share/ansible'], forks=10, become=None, + become_method=None, become_user=None, check=False, diff=False) + # required for + # https://github.com/ansible/ansible/blob/devel/lib/ansible/inventory/manager.py#L204 + sources = ','.join(host_list) + if len(host_list) == 1: + sources += ',' + + # initialize needed objects + loader = DataLoader() # Takes care of finding and reading yaml, json and ini files + passwords = dict(vault_pass='secret') + + # Instantiate our ResultsCollectorJSONCallback for handling results as they come in. Ansible expects this to be one of its main display outlets + results_callback = ResultsCollectorJSONCallback() + + # create inventory, use path to host config file as source or hosts in a comma separated string + inventory = InventoryManager(loader=loader, sources=sources) + + # variable manager takes care of merging all the different sources to give you a unified view of variables available in each context + variable_manager = VariableManager(loader=loader, inventory=inventory) + + # instantiate task queue manager, which takes care of forking and setting up all objects to iterate over host list and tasks + # IMPORTANT: This also adds library dirs paths to the module loader + # IMPORTANT: and so it must be initialized before calling `Play.load()`. + tqm = TaskQueueManager( + inventory=inventory, + variable_manager=variable_manager, + loader=loader, + passwords=passwords, + # Use our custom callback instead of the ``default`` callback plugin, which prints to stdout + stdout_callback=results_callback, + ) + + # create data structure that represents our play, including tasks, this is basically what our YAML loader does internally. + play_source = dict( + name="Ansible Play", + hosts=host_list, + gather_facts='no', + tasks=[ + dict(action=dict(module='shell', args='ls'), register='shell_out'), + dict(action=dict(module='debug', args=dict( + msg='{{shell_out.stdout}}'))), + dict(action=dict(module='command', args=dict(cmd='/usr/bin/uptime'))), + ] + ) + + # Create play object, playbook objects use .load instead of init or new methods, + # this will also automatically create the task objects from the info provided in play_source + play = Play().load(play_source, variable_manager=variable_manager, loader=loader) + + # Actually run it + try: + # most interesting data for a play is actually sent to the callback's methods + result = tqm.run(play) + finally: + # we always need to cleanup child procs and the structures we use to communicate with them + tqm.cleanup() + if loader: + loader.cleanup_all_tmp_files() + + # Remove ansible tmpdir + shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) + + print("UP ***********") + for host, result in results_callback.host_ok.items(): + print('{0} >>> {1}'.format(host, result._result['stdout'])) + + print("FAILED *******") + for host, result in results_callback.host_failed.items(): + print('{0} >>> {1}'.format(host, result._result['msg'])) + + print("DOWN *********") + for host, result in results_callback.host_unreachable.items(): + print('{0} >>> {1}'.format(host, result._result['msg'])) + + +if __name__ == '__main__': + main() diff --git a/scripts/common/ansible/mylog.py b/scripts/common/ansible/mylog.py new file mode 100644 index 0000000..a251f70 --- /dev/null +++ b/scripts/common/ansible/mylog.py @@ -0,0 +1,198 @@ +from datetime import datetime +from ansible.plugins.callback import CallbackBase + +# A custom object to store to the database +from some_project.storage import Logs + + +class PlayLogger: + """Store log output in a single object. + We create a new object per Ansible run + """ + + def __init__(self): + self.log = '' + self.runtime = 0 + + def append(self, log_line): + """append to log""" + self.log += log_line+"\n\n" + + def banner(self, msg): + """Output Trailing Stars""" + width = 78 - len(msg) + if width < 3: + width = 3 + filler = "*" * width + return "\n%s %s " % (msg, filler) + + +class CallbackModule(CallbackBase): + """ + Reference: https://github.com/ansible/ansible/blob/v2.0.0.2-1/lib/ansible/plugins/callback/default.py + """ + + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'stored' + CALLBACK_NAME = 'database' + + def __init__(self): + super(CallbackModule, self).__init__() + self.logger = PlayLogger() + self.start_time = datetime.now() + + def v2_runner_on_failed(self, result, ignore_errors=False): + delegated_vars = result._result.get('_ansible_delegated_vars', None) + + # Catch an exception + # This may never be called because default handler deletes + # the exception, since Ansible thinks it knows better + if 'exception' in result._result: + # Extract the error message and log it + error = result._result['exception'].strip().split('\n')[-1] + self.logger.append(error) + + # Remove the exception from the result so it's not shown every time + del result._result['exception'] + + # Else log the reason for the failure + if result._task.loop and 'results' in result._result: + # item_on_failed, item_on_skipped, item_on_ok + self._process_items(result) + else: + if delegated_vars: + self.logger.append("fatal: [%s -> %s]: FAILED! => %s" % (result._host.get_name( + ), delegated_vars['ansible_host'], self._dump_results(result._result))) + else: + self.logger.append("fatal: [%s]: FAILED! => %s" % ( + result._host.get_name(), self._dump_results(result._result))) + + def v2_runner_on_ok(self, result): + self._clean_results(result._result, result._task.action) + delegated_vars = result._result.get('_ansible_delegated_vars', None) + if result._task.action == 'include': + return + elif result._result.get('changed', False): + if delegated_vars: + msg = "changed: [%s -> %s]" % (result._host.get_name(), + delegated_vars['ansible_host']) + else: + msg = "changed: [%s]" % result._host.get_name() + else: + if delegated_vars: + msg = "ok: [%s -> %s]" % (result._host.get_name(), + delegated_vars['ansible_host']) + else: + msg = "ok: [%s]" % result._host.get_name() + + if result._task.loop and 'results' in result._result: + # item_on_failed, item_on_skipped, item_on_ok + self._process_items(result) + else: + self.logger.append(msg) + + def v2_runner_on_skipped(self, result): + if result._task.loop and 'results' in result._result: + # item_on_failed, item_on_skipped, item_on_ok + self._process_items(result) + else: + msg = "skipping: [%s]" % result._host.get_name() + self.logger.append(msg) + + def v2_runner_on_unreachable(self, result): + delegated_vars = result._result.get('_ansible_delegated_vars', None) + if delegated_vars: + self.logger.append("fatal: [%s -> %s]: UNREACHABLE! => %s" % (result._host.get_name( + ), delegated_vars['ansible_host'], self._dump_results(result._result))) + else: + self.logger.append("fatal: [%s]: UNREACHABLE! => %s" % ( + result._host.get_name(), self._dump_results(result._result))) + + def v2_runner_on_no_hosts(self, task): + self.logger.append("skipping: no hosts matched") + + def v2_playbook_on_task_start(self, task, is_conditional): + self.logger.append("TASK [%s]" % task.get_name().strip()) + + def v2_playbook_on_play_start(self, play): + name = play.get_name().strip() + if not name: + msg = "PLAY" + else: + msg = "PLAY [%s]" % name + + self.logger.append(msg) + + def v2_playbook_item_on_ok(self, result): + delegated_vars = result._result.get('_ansible_delegated_vars', None) + if result._task.action == 'include': + return + elif result._result.get('changed', False): + if delegated_vars: + msg = "changed: [%s -> %s]" % (result._host.get_name(), + delegated_vars['ansible_host']) + else: + msg = "changed: [%s]" % result._host.get_name() + else: + if delegated_vars: + msg = "ok: [%s -> %s]" % (result._host.get_name(), + delegated_vars['ansible_host']) + else: + msg = "ok: [%s]" % result._host.get_name() + + msg += " => (item=%s)" % (result._result['item']) + + self.logger.append(msg) + + def v2_playbook_item_on_failed(self, result): + delegated_vars = result._result.get('_ansible_delegated_vars', None) + if 'exception' in result._result: + # Extract the error message and log it + error = result._result['exception'].strip().split('\n')[-1] + self.logger.append(error) + + # Remove the exception from the result so it's not shown every time + del result._result['exception'] + + if delegated_vars: + self.logger.append("failed: [%s -> %s] => (item=%s) => %s" % (result._host.get_name( + ), delegated_vars['ansible_host'], result._result['item'], self._dump_results(result._result))) + else: + self.logger.append("failed: [%s] => (item=%s) => %s" % (result._host.get_name( + ), result._result['item'], self._dump_results(result._result))) + + def v2_playbook_item_on_skipped(self, result): + msg = "skipping: [%s] => (item=%s) " % ( + result._host.get_name(), result._result['item']) + self.logger.append(msg) + + def v2_playbook_on_stats(self, stats): + run_time = datetime.now() - self.start_time + # returns an int, unlike run_time.total_seconds() + self.logger.runtime = run_time.seconds + + hosts = sorted(stats.processed.keys()) + for h in hosts: + t = stats.summarize(h) + + msg = "PLAY RECAP [%s] : %s %s %s %s %s" % ( + h, + "ok: %s" % (t['ok']), + "changed: %s" % (t['changed']), + "unreachable: %s" % (t['unreachable']), + "skipped: %s" % (t['skipped']), + "failed: %s" % (t['failures']), + ) + + self.logger.append(msg) + + def record_logs(self, user_id, success=False): + """ + Special callback added to this callback plugin + Called by Runner objet + :param user_id: + :return: + """ + + log_storage = Logs() + return log_storage.save_log(user_id, self.logger.log, self.logger.runtime, success) diff --git a/scripts/common/ansible/run_playbook.py b/scripts/common/ansible/run_playbook.py new file mode 100644 index 0000000..f8905b6 --- /dev/null +++ b/scripts/common/ansible/run_playbook.py @@ -0,0 +1,23 @@ +from .task import Runner +# You may want this to run as user root instead +# or make this an environmental variable, or +# a CLI prompt. Whatever you want! +become_user_password = 'foo-whatever' +run_data = { + "user_id": 12345, + 'foo': 'bar', + 'baz': 'cux-or-whatever-this-one-is' +} + + +runner = Runner(hostnames='192.168.10.233' + playbook='run.yaml', + private_key='/home/user/.ssh/id_whatever', + run_data=run_data, + become_pass=become_user_password, + verbosity=0) + + +stats = runner.run() + +# Maybe do something with stats here? If you want! diff --git a/scripts/common/ansible/task.py b/scripts/common/ansible/task.py new file mode 100644 index 0000000..06d358e --- /dev/null +++ b/scripts/common/ansible/task.py @@ -0,0 +1,159 @@ +import os +from tempfile import NamedTemporaryFile +from ansible.inventory import Inventory +from ansible.vars import VariableManager +from ansible.parsing.dataloader import DataLoader +from ansible.executor import playbook_executor +from ansible.utils.display import Display + + +class Options(object): + """ + Options class to replace Ansible OptParser + """ + + def __init__(self, verbosity=None, inventory=None, listhosts=None, subset=None, module_paths=None, extra_vars=None, + forks=None, ask_vault_pass=None, vault_password_files=None, new_vault_password_file=None, + output_file=None, tags=None, skip_tags=None, one_line=None, tree=None, ask_sudo_pass=None, ask_su_pass=None, + sudo=None, sudo_user=None, become=None, become_method=None, become_user=None, become_ask_pass=None, + ask_pass=None, private_key_file=None, remote_user=None, connection=None, timeout=None, ssh_common_args=None, + sftp_extra_args=None, scp_extra_args=None, ssh_extra_args=None, poll_interval=None, seconds=None, check=None, + syntax=None, diff=None, force_handlers=None, flush_cache=None, listtasks=None, listtags=None, module_path=None): + self.verbosity = verbosity + self.inventory = inventory + self.listhosts = listhosts + self.subset = subset + self.module_paths = module_paths + self.extra_vars = extra_vars + self.forks = forks + self.ask_vault_pass = ask_vault_pass + self.vault_password_files = vault_password_files + self.new_vault_password_file = new_vault_password_file + self.output_file = output_file + self.tags = tags + self.skip_tags = skip_tags + self.one_line = one_line + self.tree = tree + self.ask_sudo_pass = ask_sudo_pass + self.ask_su_pass = ask_su_pass + self.sudo = sudo + self.sudo_user = sudo_user + self.become = become + self.become_method = become_method + self.become_user = become_user + self.become_ask_pass = become_ask_pass + self.ask_pass = ask_pass + self.private_key_file = private_key_file + self.remote_user = remote_user + self.connection = connection + self.timeout = timeout + self.ssh_common_args = ssh_common_args + self.sftp_extra_args = sftp_extra_args + self.scp_extra_args = scp_extra_args + self.ssh_extra_args = ssh_extra_args + self.poll_interval = poll_interval + self.seconds = seconds + self.check = check + self.syntax = syntax + self.diff = diff + self.force_handlers = force_handlers + self.flush_cache = flush_cache + self.listtasks = listtasks + self.listtags = listtags + self.module_path = module_path + + +class Runner(object): + + def __init__(self, hostnames, playbook, private_key_file, run_data, become_pass, verbosity=0): + + self.run_data = run_data + + self.options = Options() + self.options.private_key_file = private_key_file + self.options.verbosity = verbosity + self.options.connection = 'ssh' # Need a connection type "smart" or "ssh" + self.options.become = True + self.options.become_method = 'sudo' + self.options.become_user = 'root' + + # Set global verbosity + self.display = Display() + self.display.verbosity = self.options.verbosity + # Executor appears to have it's own + # verbosity object/setting as well + playbook_executor.verbosity = self.options.verbosity + + # Become Pass Needed if not logging in as user root + passwords = {'become_pass': become_pass} + + # Gets data from YAML/JSON files + self.loader = DataLoader() + self.loader.set_vault_password(os.environ['VAULT_PASS']) + + # All the variables from all the various places + self.variable_manager = VariableManager() + self.variable_manager.extra_vars = self.run_data + + # Parse hosts, I haven't found a good way to + # pass hosts in without using a parsed template :( + # (Maybe you know how?) + self.hosts = NamedTemporaryFile(delete=False) + self.hosts.write("""[run_hosts] +%s +""" % hostnames) + self.hosts.close() + + # This was my attempt to pass in hosts directly. + # + # Also Note: In py2.7, "isinstance(foo, str)" is valid for + # latin chars only. Luckily, hostnames are + # ascii-only, which overlaps latin charset + ## if isinstance(hostnames, str): + ## hostnames = {"customers": {"hosts": [hostnames]}} + + # Set inventory, using most of above objects + self.inventory = Inventory( + loader=self.loader, variable_manager=self.variable_manager, host_list=self.hosts.name) + self.variable_manager.set_inventory(self.inventory) + + # Playbook to run. Assumes it is + # local to this python file + pb_dir = os.path.dirname(__file__) + playbook = "%s/%s" % (pb_dir, playbook) + + # Setup playbook executor, but don't run until run() called + self.pbex = playbook_executor.PlaybookExecutor( + playbooks=[playbook], + inventory=self.inventory, + variable_manager=self.variable_manager, + loader=self.loader, + options=self.options, + passwords=passwords) + + def run(self): + # Results of PlaybookExecutor + self.pbex.run() + stats = self.pbex._tqm._stats + + # Test if success for record_logs + run_success = True + hosts = sorted(stats.processed.keys()) + for h in hosts: + t = stats.summarize(h) + if t['unreachable'] > 0 or t['failures'] > 0: + run_success = False + + # Dirty hack to send callback to save logs with data we want + # Note that function "record_logs" is one I created and put into + # the playbook callback file + self.pbex._tqm.send_callback( + 'record_logs', + user_id=self.run_data['user_id'], + success=run_success + ) + + # Remove created temporary files + os.remove(self.hosts.name) + + return stats diff --git a/scripts/common/myansible.py b/scripts/common/myansible.py new file mode 100644 index 0000000..854a5b7 --- /dev/null +++ b/scripts/common/myansible.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import tempfile +from collections import namedtuple +from ansible.parsing.dataloader import DataLoader +from ansible.vars.manager import VariableManager +from ansible.inventory.manager import InventoryManager +from ansible.playbook.play import Play +from ansible.executor.playbook_executor import PlaybookExecutor +from ansible.executor.task_queue_manager import TaskQueueManager +from ansible.plugins.callback import CallbackBase +import json +import pdb +import copy +import time + + +def write_host(ip_addr): + host_file = "%s/host%s" % ('/tmp', time.strftime("%Y%m%d%H%M%s")) + with open(host_file, 'a+') as f: + for x in ip_addr.split(','): + f.writelines(x + '\n') + return host_file + + +class ResultsCallback(CallbackBase): + def __init__(self, *args, **kwargs): + super(ResultsCallback, self).__init__(*args, **kwargs) + self.task_ok = {} + self.task_unreachable = {} + self.task_failed = {} + self.task_skipped = {} + self.task_stats = {} + # self.host_ok = {} + # self.host_unreachable = {} + # self.host_failed = {} + + def v2_runner_on_unreachable(self, result): + self.task_unreachable[result._host.get_name()] = result + + def v2_runner_on_ok(self, result, *args, **kwargs): + self.task_ok[result._host.get_name()] = result + + def v2_runner_on_failed(self, result, *args, **kwargs): + self.task_failed[result._host.get_name()] = result + + def v2_runner_on_skipped(self, result, *args, **kwargs): + self.task_skipped[result._host.get_name()] = result + + def v2_runner_on_stats(self, result, *args, **kwargs): + self.task_stats[result._host.get_name()] = result + + +class AnsibleAPI(object): + def __init__(self, hostfile, *args, **kwargs): + self.loader = DataLoader() + self.results_callback = ResultsCallback() + # if not os.path.isfile(hostfile): + # raise Exception("%s file not found!" % hostfile) + self.inventory = InventoryManager( + loader=self.loader, sources=[hostfile]) + self.variable_manager = VariableManager( + loader=self.loader, inventory=self.inventory) + self.passwords = None + Options = namedtuple('Options', + ['connection', + 'remote_user', + 'ask_sudo_pass', + 'verbosity', + 'ack_pass', + 'module_path', + 'forks', + 'become', + 'become_method', + 'become_user', + 'check', + 'listhosts', + 'listtasks', + 'listtags', + 'syntax', + 'sudo_user', + 'sudo', + 'diff']) + # 初始化需要的对象 + self.options = Options(connection='smart', + remote_user=None, + ack_pass=None, + sudo_user=None, + forks=5, + sudo=None, + ask_sudo_pass=False, + verbosity=5, + module_path=None, + become=None, + become_method=None, + become_user=None, + check=False, + diff=False, + listhosts=None, + listtasks=None, + listtags=None, + syntax=None) + + @staticmethod + def deal_result(info): + host_ips = list(info.get('success').keys()) + info['success'] = host_ips + + error_ips = info.get('failed') + error_msg = {} + for key, value in error_ips.items(): + temp = {} + temp[key] = value.get('stderr') + error_msg.update(temp) + info['failed'] = error_msg + return info + # return json.dumps(info) + + def run(self, module_name, module_args): + play_source = dict( + name="Ansible Play", + hosts='all', + gather_facts='no', + tasks=[ + dict(action=dict(module=module_name, args=module_args), + register='shell_out'), + # dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}'))) + ] + ) + play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader) + + tqm = None + try: + tqm = TaskQueueManager( + inventory=self.inventory, + variable_manager=self.variable_manager, + loader=self.loader, + options=self.options, + passwords=self.passwords, + stdout_callback=self.results_callback, + ) + result = tqm.run(play) + finally: + if tqm is not None: + tqm.cleanup() + + # 定义字典用于接收或者处理结果 + result_raw = {'success': {}, 'failed': {}, + 'unreachable': {}, 'skipped': {}, 'status': {}} + + # 循环打印这个结果,success,failed,unreachable需要每个都定义一个 + for host, result in self.results_callback.task_ok.items(): + result_raw['success'][host] = result._result + for host, result in self.results_callback.task_failed.items(): + result_raw['failed'][host] = result._result + for host, result in self.results_callback.task_unreachable.items(): + result_raw['unreachable'][host] = result._result + + result_full = copy.deepcopy(result_raw) + result_json = self.deal_result(result_raw) + if not result_json['failed']: + return result_json + else: + return result_full + # return result_raw + + def run_playbook(self, file, **kwargs): + if not os.path.isfile(file): + raise Exception("%s file not found!" % file) + try: + # extra_vars = {} # 额外的参数 sudoers.yml以及模板中的参数,它对应ansible-playbook test.yml --extra-vars "host='aa' name='cc' " + self.variable_manager.extra_vars = kwargs + + playbook = PlaybookExecutor(playbooks=['' + file], inventory=self.inventory, + variable_manager=self.variable_manager, + loader=self.loader, options=self.options, passwords=self.passwords) + + playbook._tqm._stdout_callback = self.results_callback + playbook.run() + except Exception as e: + print("error:", e.message) + + # 定义字典用于接收或者处理结果 + result_raw = {'success': {}, 'failed': {}, + 'unreachable': {}, 'skipped': {}, 'status': {}} + + # 循环打印这个结果,success,failed,unreachable需要每个都定义一个 + for host, result in self.results_callback.task_ok.items(): + result_raw['success'][host] = result._result + for host, result in self.results_callback.task_failed.items(): + result_raw['failed'][host] = result._result + for host, result in self.results_callback.task_unreachable.items(): + result_raw['unreachable'][host] = result._result + + # for host, result in self.results_callback.task_skipped.items(): + # result_raw['skipped'][host] = result._result + # + # for host, result in self.results_callback.task_stats.items(): + # result_raw['status'][host] = result._result + + result_full = copy.deepcopy(result_raw) + result_json = self.deal_result(result_raw) + if not result_json['failed']: + return result_json + else: + return result_full + + +class AnsiInterface(AnsibleAPI): + def __init__(self, hostfile, *args, **kwargs): + super(AnsiInterface, self).__init__(hostfile, *args, **kwargs) + + def copy_file(self, src=None, dest=None): + """ + copy file + """ + module_args = "src=%s dest=%s" % (src, dest) + result = self.run('copy', module_args) + return result + + def exec_command(self, cmds): + """ + commands + """ + result = self.run('command', cmds) + return result + + def exec_script(self, path): + """ + 在远程主机执行shell命令或者.sh脚本 + """ + result = self.run('shell', path) + return result