1# Copyright (c) 2017 Ansible Project 2# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 3 4from __future__ import (absolute_import, division, print_function) 5__metaclass__ = type 6 7import os 8import time 9 10from ansible import constants as C 11from ansible.executor.module_common import get_action_args_with_defaults 12from ansible.module_utils.parsing.convert_bool import boolean 13from ansible.plugins.action import ActionBase 14from ansible.utils.vars import merge_hash 15 16 17class ActionModule(ActionBase): 18 19 def _get_module_args(self, fact_module, task_vars): 20 21 mod_args = self._task.args.copy() 22 23 # deal with 'setup specific arguments' 24 if fact_module not in C._ACTION_SETUP: 25 # network facts modules must support gather_subset 26 if self._connection._load_name not in ('network_cli', 'httpapi', 'netconf'): 27 subset = mod_args.pop('gather_subset', None) 28 if subset not in ('all', ['all']): 29 self._display.warning('Ignoring subset(%s) for %s' % (subset, fact_module)) 30 31 timeout = mod_args.pop('gather_timeout', None) 32 if timeout is not None: 33 self._display.warning('Ignoring timeout(%s) for %s' % (timeout, fact_module)) 34 35 fact_filter = mod_args.pop('filter', None) 36 if fact_filter is not None: 37 self._display.warning('Ignoring filter(%s) for %s' % (fact_filter, fact_module)) 38 39 # Strip out keys with ``None`` values, effectively mimicking ``omit`` behavior 40 # This ensures we don't pass a ``None`` value as an argument expecting a specific type 41 mod_args = dict((k, v) for k, v in mod_args.items() if v is not None) 42 43 # handle module defaults 44 redirect_list = self._shared_loader_obj.module_loader.find_plugin_with_context( 45 fact_module, collection_list=self._task.collections 46 ).redirect_list 47 48 mod_args = get_action_args_with_defaults( 49 fact_module, mod_args, self._task.module_defaults, self._templar, redirect_list 50 ) 51 52 return mod_args 53 54 def _combine_task_result(self, result, task_result): 55 filtered_res = { 56 'ansible_facts': task_result.get('ansible_facts', {}), 57 'warnings': task_result.get('warnings', []), 58 'deprecations': task_result.get('deprecations', []), 59 } 60 61 # on conflict the last plugin processed wins, but try to do deep merge and append to lists. 62 return merge_hash(result, filtered_res, list_merge='append_rp') 63 64 def run(self, tmp=None, task_vars=None): 65 66 self._supports_check_mode = True 67 68 result = super(ActionModule, self).run(tmp, task_vars) 69 result['ansible_facts'] = {} 70 71 # copy the value with list() so we don't mutate the config 72 modules = list(C.config.get_config_value('FACTS_MODULES', variables=task_vars)) 73 74 parallel = task_vars.pop('ansible_facts_parallel', self._task.args.pop('parallel', None)) 75 if 'smart' in modules: 76 connection_map = C.config.get_config_value('CONNECTION_FACTS_MODULES', variables=task_vars) 77 network_os = self._task.args.get('network_os', task_vars.get('ansible_network_os', task_vars.get('ansible_facts', {}).get('network_os'))) 78 modules.extend([connection_map.get(network_os or self._connection._load_name, 'ansible.legacy.setup')]) 79 modules.pop(modules.index('smart')) 80 81 failed = {} 82 skipped = {} 83 84 if parallel is None and len(modules) >= 1: 85 parallel = True 86 else: 87 parallel = boolean(parallel) 88 89 if parallel: 90 # serially execute each module 91 for fact_module in modules: 92 # just one module, no need for fancy async 93 mod_args = self._get_module_args(fact_module, task_vars) 94 res = self._execute_module(module_name=fact_module, module_args=mod_args, task_vars=task_vars, wrap_async=False) 95 if res.get('failed', False): 96 failed[fact_module] = res 97 elif res.get('skipped', False): 98 skipped[fact_module] = res 99 else: 100 result = self._combine_task_result(result, res) 101 102 self._remove_tmp_path(self._connection._shell.tmpdir) 103 else: 104 # do it async 105 jobs = {} 106 for fact_module in modules: 107 mod_args = self._get_module_args(fact_module, task_vars) 108 self._display.vvvv("Running %s" % fact_module) 109 jobs[fact_module] = (self._execute_module(module_name=fact_module, module_args=mod_args, task_vars=task_vars, wrap_async=True)) 110 111 while jobs: 112 for module in jobs: 113 poll_args = {'jid': jobs[module]['ansible_job_id'], '_async_dir': os.path.dirname(jobs[module]['results_file'])} 114 res = self._execute_module(module_name='ansible.legacy.async_status', module_args=poll_args, task_vars=task_vars, wrap_async=False) 115 if res.get('finished', 0) == 1: 116 if res.get('failed', False): 117 failed[module] = res 118 elif res.get('skipped', False): 119 skipped[module] = res 120 else: 121 result = self._combine_task_result(result, res) 122 del jobs[module] 123 break 124 else: 125 time.sleep(0.1) 126 else: 127 time.sleep(0.5) 128 129 if skipped: 130 result['msg'] = "The following modules were skipped: %s\n" % (', '.join(skipped.keys())) 131 result['skipped_modules'] = skipped 132 if len(skipped) == len(modules): 133 result['skipped'] = True 134 135 if failed: 136 result['failed'] = True 137 result['msg'] = "The following modules failed to execute: %s\n" % (', '.join(failed.keys())) 138 result['failed_modules'] = failed 139 140 # tell executor facts were gathered 141 result['ansible_facts']['_ansible_facts_gathered'] = True 142 143 # hack to keep --verbose from showing all the setup module result 144 result['_ansible_verbose_override'] = True 145 146 return result 147