diff options
Diffstat (limited to 'lib/ansible/plugins/action/gather_facts.py')
-rw-r--r-- | lib/ansible/plugins/action/gather_facts.py | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/lib/ansible/plugins/action/gather_facts.py b/lib/ansible/plugins/action/gather_facts.py new file mode 100644 index 0000000..3ff7beb --- /dev/null +++ b/lib/ansible/plugins/action/gather_facts.py @@ -0,0 +1,152 @@ +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import time + +from ansible import constants as C +from ansible.executor.module_common import get_action_args_with_defaults +from ansible.module_utils.parsing.convert_bool import boolean +from ansible.plugins.action import ActionBase +from ansible.utils.vars import merge_hash + + +class ActionModule(ActionBase): + + def _get_module_args(self, fact_module, task_vars): + + mod_args = self._task.args.copy() + + # deal with 'setup specific arguments' + if fact_module not in C._ACTION_SETUP: + # TODO: remove in favor of controller side argspec detecing valid arguments + # network facts modules must support gather_subset + try: + name = self._connection.ansible_name.removeprefix('ansible.netcommon.') + except AttributeError: + name = self._connection._load_name.split('.')[-1] + if name not in ('network_cli', 'httpapi', 'netconf'): + subset = mod_args.pop('gather_subset', None) + if subset not in ('all', ['all']): + self._display.warning('Ignoring subset(%s) for %s' % (subset, fact_module)) + + timeout = mod_args.pop('gather_timeout', None) + if timeout is not None: + self._display.warning('Ignoring timeout(%s) for %s' % (timeout, fact_module)) + + fact_filter = mod_args.pop('filter', None) + if fact_filter is not None: + self._display.warning('Ignoring filter(%s) for %s' % (fact_filter, fact_module)) + + # Strip out keys with ``None`` values, effectively mimicking ``omit`` behavior + # This ensures we don't pass a ``None`` value as an argument expecting a specific type + mod_args = dict((k, v) for k, v in mod_args.items() if v is not None) + + # handle module defaults + resolved_fact_module = self._shared_loader_obj.module_loader.find_plugin_with_context( + fact_module, collection_list=self._task.collections + ).resolved_fqcn + + mod_args = get_action_args_with_defaults( + resolved_fact_module, mod_args, self._task.module_defaults, self._templar, + action_groups=self._task._parent._play._action_groups + ) + + return mod_args + + def _combine_task_result(self, result, task_result): + filtered_res = { + 'ansible_facts': task_result.get('ansible_facts', {}), + 'warnings': task_result.get('warnings', []), + 'deprecations': task_result.get('deprecations', []), + } + + # on conflict the last plugin processed wins, but try to do deep merge and append to lists. + return merge_hash(result, filtered_res, list_merge='append_rp') + + def run(self, tmp=None, task_vars=None): + + self._supports_check_mode = True + + result = super(ActionModule, self).run(tmp, task_vars) + result['ansible_facts'] = {} + + # copy the value with list() so we don't mutate the config + modules = list(C.config.get_config_value('FACTS_MODULES', variables=task_vars)) + + parallel = task_vars.pop('ansible_facts_parallel', self._task.args.pop('parallel', None)) + if 'smart' in modules: + connection_map = C.config.get_config_value('CONNECTION_FACTS_MODULES', variables=task_vars) + network_os = self._task.args.get('network_os', task_vars.get('ansible_network_os', task_vars.get('ansible_facts', {}).get('network_os'))) + modules.extend([connection_map.get(network_os or self._connection.ansible_name, 'ansible.legacy.setup')]) + modules.pop(modules.index('smart')) + + failed = {} + skipped = {} + + if parallel is None and len(modules) >= 1: + parallel = True + else: + parallel = boolean(parallel) + + if parallel: + # serially execute each module + for fact_module in modules: + # just one module, no need for fancy async + mod_args = self._get_module_args(fact_module, task_vars) + res = self._execute_module(module_name=fact_module, module_args=mod_args, task_vars=task_vars, wrap_async=False) + if res.get('failed', False): + failed[fact_module] = res + elif res.get('skipped', False): + skipped[fact_module] = res + else: + result = self._combine_task_result(result, res) + + self._remove_tmp_path(self._connection._shell.tmpdir) + else: + # do it async + jobs = {} + for fact_module in modules: + mod_args = self._get_module_args(fact_module, task_vars) + self._display.vvvv("Running %s" % fact_module) + jobs[fact_module] = (self._execute_module(module_name=fact_module, module_args=mod_args, task_vars=task_vars, wrap_async=True)) + + while jobs: + for module in jobs: + poll_args = {'jid': jobs[module]['ansible_job_id'], '_async_dir': os.path.dirname(jobs[module]['results_file'])} + res = self._execute_module(module_name='ansible.legacy.async_status', module_args=poll_args, task_vars=task_vars, wrap_async=False) + if res.get('finished', 0) == 1: + if res.get('failed', False): + failed[module] = res + elif res.get('skipped', False): + skipped[module] = res + else: + result = self._combine_task_result(result, res) + del jobs[module] + break + else: + time.sleep(0.1) + else: + time.sleep(0.5) + + if skipped: + result['msg'] = "The following modules were skipped: %s\n" % (', '.join(skipped.keys())) + result['skipped_modules'] = skipped + if len(skipped) == len(modules): + result['skipped'] = True + + if failed: + result['failed'] = True + result['msg'] = "The following modules failed to execute: %s\n" % (', '.join(failed.keys())) + result['failed_modules'] = failed + + # tell executor facts were gathered + result['ansible_facts']['_ansible_facts_gathered'] = True + + # hack to keep --verbose from showing all the setup module result + result['_ansible_verbose_override'] = True + + return result |