# ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals import os from collections import namedtuple from ansible.executor.task_queue_manager import TaskQueueManager 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 .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: """ 用于执行AnsiblePlaybook的接口.简化Playbook对象的使用. """ # 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.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() 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') 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 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 raise AnsibleError(err) 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 ) 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'}, ...}, ] :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=gather_facts, tasks=clean_tasks ) play = Play().load( play_source, variable_manager=self.variable_manager, loader=self.loader, ) tqm = TaskQueueManager( inventory=self.inventory, variable_manager=self.variable_manager, loader=self.loader, options=self.options, stdout_callback=results_callback, passwords=self.options.passwords, ) try: tqm.run(play) return results_callback except Exception as e: raise AnsibleError(e) finally: tqm.cleanup() self.loader.cleanup_all_tmp_files() class CommandRunner(AdHocRunner): results_callback_class = CommandResultCallback modules_choices = ('shell', 'raw', 'command', 'script') 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)