mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-23 05:10:22 -07:00
Configurable and parallel gather facts (#49399)
* Configurable list of facts modules (#31783) - allow for args dict for specific modules - add way to pass parameters - avoid facts poluting test - move to 'facts gathered' flag - add 'gathering' setting tests - allow parallel option in case serialization is too slow - added support to automatically map network facts uses "smart" connection mapping
This commit is contained in:
parent
90bcff3d92
commit
8940732b58
12 changed files with 268 additions and 3 deletions
112
lib/ansible/plugins/action/gather_facts.py
Normal file
112
lib/ansible/plugins/action/gather_facts.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
# 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.plugins.action import ActionBase
|
||||
from ansible.utils.vars import combine_vars
|
||||
|
||||
|
||||
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 != 'setup':
|
||||
|
||||
# network facts modules must support gather_subset
|
||||
if self._connection._load_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))
|
||||
|
||||
return mod_args
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
self._supports_check_mode = True
|
||||
|
||||
result = super(ActionModule, self).run(tmp, task_vars)
|
||||
result['ansible_facts'] = {}
|
||||
|
||||
modules = 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)
|
||||
modules.extend([connection_map.get(self._connection._load_name, 'setup')])
|
||||
modules.pop(modules.index('smart'))
|
||||
|
||||
failed = {}
|
||||
skipped = {}
|
||||
if parallel is False or (len(modules) == 1 and parallel is None):
|
||||
# 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.get('msg')
|
||||
elif res.get('skipped', False):
|
||||
skipped[fact_module] = res.get('msg')
|
||||
else:
|
||||
result = combine_vars(result, {'ansible_facts': res.get('ansible_facts', {})})
|
||||
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='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.get('msg')
|
||||
elif res.get('skipped', False):
|
||||
skipped[module] = res.get('msg')
|
||||
else:
|
||||
result = combine_vars(result, {'ansible_facts': res.get('ansible_facts', {})})
|
||||
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()))
|
||||
for skip in skipped:
|
||||
result['msg'] += ' %s: %s\n' % (skip, skipped[skip])
|
||||
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()))
|
||||
for fail in failed:
|
||||
result['msg'] += ' %s: %s\n' % (fail, failed[fail])
|
||||
|
||||
# tell executor facts were gathered
|
||||
result['ansible_facts']['_ansible_facts_gathered'] = True
|
||||
|
||||
return result
|
|
@ -376,7 +376,7 @@ class PluginLoader:
|
|||
from ansible.vars.reserved import is_reserved_name
|
||||
|
||||
plugin = self._find_plugin(name, mod_type=mod_type, ignore_deprecated=ignore_deprecated, check_aliases=check_aliases)
|
||||
if plugin and self.package == 'ansible.modules' and is_reserved_name(name):
|
||||
if plugin and self.package == 'ansible.modules' and name not in ('gather_facts',) and is_reserved_name(name):
|
||||
raise AnsibleError(
|
||||
'Module "%s" shadows the name of a reserved keyword. Please rename or remove this module. Found at %s' % (name, plugin)
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue