# ~*~ coding: utf-8 ~*~ 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 .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', 'gathering', 'remote_tmp', ]) 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, gathering='implicit', remote_tmp='/tmp/.ansible' ) return options # Jumpserver not use playbook class PlayBookRunner: """ 用于执行AnsiblePlaybook的接口.简化Playbook对象的使用. """ # Default results callback results_callback_class = PlaybookResultCallBack loader_class = DataLoader variable_manager_class = VariableManager options = get_default_options() def __init__(self, inventory=None, options=None): """ :param options: Ansible options like ansible.cfg :param inventory: Ansible inventory """ if options: self.options = options C.RETRY_FILES_ENABLED = False self.inventory = inventory 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 loader_class = DataLoader variable_manager_class = VariableManager options = get_default_options() default_options = get_default_options() def __init__(self, inventory, options=None): if options: self.options = options self.inventory = inventory self.loader = DataLoader() 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 pattern: raise AnsibleError("Pattern `{}` is not valid!".format(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 clean_tasks(self, tasks): cleaned_tasks = [] for task in tasks: self.check_module_args(task['action']['module'], task['action'].get('args')) cleaned_tasks.append(task) return cleaned_tasks 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: """ self.check_pattern(pattern) results_callback = self.results_callback_class() cleaned_tasks = self.clean_tasks(tasks) play_source = dict( name=play_name, hosts=pattern, gather_facts=gather_facts, tasks=cleaned_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, ) logger.debug("Get inventory matched hosts: {}".format( self.inventory.get_matched_hosts(pattern) )) 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)