Coverage for lib/ansible/plugins/loader.py : 75%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com> # (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> and others # (c) 2017, Toshio Kuratomi <tkuratomi@ansible.com> # (c) 2017 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' PluginLoader loads plugins from the configured plugin directories.
It searches for plugins by iterating through the combined list of play basedirs, configured paths, and the python path. The first match is used. '''
# FIXME: remove alias dict in favor of alias by symlink?
config = []
''' Deserializer. '''
class_name = data.get('class_name') package = data.get('package') config = data.get('config') subdir = data.get('subdir') aliases = data.get('aliases') base_class = data.get('base_class')
PATH_CACHE[class_name] = data.get('PATH_CACHE') PLUGIN_PATH_CACHE[class_name] = data.get('PLUGIN_PATH_CACHE')
self.__init__(class_name, package, config, subdir, aliases, base_class) self._extra_dirs = data.get('_extra_dirs', []) self._searched_paths = data.get('_searched_paths', set())
''' Serializer. '''
return dict( class_name=self.class_name, base_class=self.base_class, package=self.package, config=self.config, subdir=self.subdir, aliases=self.aliases, _extra_dirs=self._extra_dirs, _searched_paths=self._searched_paths, PATH_CACHE=PATH_CACHE[self.class_name], PLUGIN_PATH_CACHE=PLUGIN_PATH_CACHE[self.class_name], )
''' Returns a string suitable for printing of the search path '''
# Uses a list to get the order right
return self.format_paths(self._get_paths(subdirs=False))
''' Gets the path of a Python package '''
return []
''' Return a list of paths to search for plugins in '''
# FIXME: This is potentially buggy if subdirs is sometimes True and sometimes False. # In current usage, everything calls this with subdirs=True except for module_utils_loader and ansible-doc # which always calls it with subdirs=False. So there currently isn't a problem with this caching.
# look in any configured plugin paths, allow one level deep for subcategories if os.path.isdir(c) and c not in ret: ret.append(c)
# look for any plugins installed in the package subtree # Note package path always gets added last so that every other type of # path is searched before it.
# HACK: because powershell modules are in the same directory # hierarchy as other modules we have to process them last. This is # because powershell only works on windows but the other modules work # anywhere (possibly including windows if the correct language # interpreter is installed). the non-powershell modules can have any # file extension and thus powershell modules are picked up in that. # The non-hack way to fix this is to have powershell modules be # a different PluginLoader/ModuleLoader. But that requires changing # other things too (known thing to change would be PATHS_CACHE, # PLUGIN_PATHS_CACHE, and MODULE_CACHE. Since those three dicts key # on the class_name and neither regular modules nor powershell modules # would have class_names, they would not work as written.
else:
# cache and return the result
''' Reads plugin docs to find configuration setting definitions, to push to config manager for later use '''
# plugins w/o class name don't support config
# FIXME: expand to other plugins, but never doc fragments # if type name != 'module_doc_fragment':
''' Adds an additional directory to the search path '''
directory = os.path.realpath(directory)
if directory is not None: if with_subdir: directory = os.path.join(directory, self.subdir) if directory not in self._extra_dirs: # append the directory and invalidate the path cache self._extra_dirs.append(directory) self._paths = None display.debug('Added %s to loader search path' % (directory))
''' Find a plugin named name '''
global _PLUGIN_FILTERS return None
suffix = mod_type # Ansible plugins that run in the controller process (most plugins) else: # Only Ansible Modules. Ansible modules can be any executable so # they can have any suffix
name = self.aliases.get(name, name)
# The particular cache to look for modules within. This matches the # requested mod_type # Cache miss. Now let's find the plugin
# TODO: Instead of using the self._paths cache (PATH_CACHE) and # self._searched_paths we could use an iterator. Before enabling that # we need to make sure we don't want to add additional directories # (add_directory()) once we start using the iterator. Currently, it # looks like _get_paths() never forces a cache refresh so if we expect # additional directories to be added later, it is buggy. except OSError as e: display.warning("Error accessing plugin paths: %s" % to_text(e))
# HACK: We have no way of executing python byte compiled files as ansible modules so specifically exclude them # FIXME: I believe this is only correct for modules and module_utils. # For all other plugins we want .pyc and .pyo should be valid
except IndexError: extension = ''
# Module found, now enter it into the caches that match this file
# Didn't find the plugin in this directory. Load modules from the next one
# if nothing is found, try finding alias/deprecated # We've already cached all the paths at this point if not ignore_deprecated and not os.path.islink(pull_cache[alias_name]): # FIXME: this is not always the case, some are just aliases display.deprecated('%s is kept for backwards compatibility but usage is discouraged. ' 'The module documentation details page may explain more about this rationale.' % name.lstrip('_')) return pull_cache[alias_name]
''' Checks if a plugin named name exists '''
# avoid collisions across plugins
# Avoids double loading, See https://github.com/ansible/ansible/issues/13110
# load plugin config data
# set extra info on the module, in case we want it later
''' instantiates a plugin of the given name using arguments '''
# The import path is hardcoded and should be the right place, # so we are not expecting an ImportError. # Check whether this obj has the required base class. except AttributeError: return None return None
except TypeError as e: if "abstract" in e.args[0]: # Abstract Base Class. The found plugin file does not # fully implement the defined interface. return None raise
''' instantiates all plugins with the same arguments '''
global _PLUGIN_FILTERS
continue
# In ``_load_module_source`` if a plugin has a duplicate name, we just return the # previously matched plugin from sys.modules, which means you are never getting both, # just one, but cached for both paths, this isn't normally a problem, except with callbacks # where it will run that single callback twice. This rejects duplicates. continue
except AttributeError as e: display.warning("Skipping plugin (%s) as it seems to be invalid: %s" % (path, to_text(e))) continue
# The import path is hardcoded and should be the right place, # so we are not expecting an ImportError. module = __import__(self.package, fromlist=[self.base_class]) # Check whether this obj has the required base class. try: plugin_class = getattr(module, self.base_class) except AttributeError: continue if not issubclass(obj, plugin_class): continue
except TypeError as e: display.warning("Skipping plugin (%s) as it seems to be incomplete: %s" % (path, to_text(e)))
else: filter_cfg = C.PLUGIN_FILTERS_CFG user_set = True
with open(filter_cfg, 'rb') as f: try: filter_data = from_yaml(f.read()) except Exception as e: display.warning(u'The plugin filter file, {0} was not parsable.' u' Skipping: {1}'.format(filter_cfg, to_text(e))) return filters
try: version = filter_data['filter_version'] except KeyError: display.warning(u'The plugin filter file, {0} was invalid.' u' Skipping.'.format(filter_cfg)) return filters
# Try to convert for people specifying version as a float instead of string version = to_text(version) version = version.strip()
if version == u'1.0': # Modules and action plugins share the same blacklist since the difference between the # two isn't visible to the users filters['ansible.modules'] = frozenset(filter_data['module_blacklist']) filters['ansible.plugins.action'] = filters['ansible.modules'] else: display.warning(u'The plugin filter file, {0} was a version not recognized by this' u' version of Ansible. Skipping.') else: display.warning(u'The plugin filter file, {0} does not exist.' u' Skipping.'.format(filter_cfg))
# Specialcase the stat module as Ansible can run very few things if stat is blacklisted. raise AnsibleError('The stat module was specified in the module blacklist file, {0}, but' ' Ansible will not function without the stat module. Please remove stat' ' from the blacklist.'.format(filter_cfg))
# TODO: All of the following is initialization code It should be moved inside of an initialization # function which is called at some point early in the ansible and ansible-playbook CLI startup.
# doc fragments first 'ModuleDocFragment', 'ansible.utils.module_docs_fragments', os.path.join(os.path.dirname(__file__), 'module_docs_fragments'), '', )
'ActionModule', 'ansible.plugins.action', C.DEFAULT_ACTION_PLUGIN_PATH, 'action_plugins', required_base_class='ActionBase', )
'CacheModule', 'ansible.plugins.cache', C.DEFAULT_CACHE_PLUGIN_PATH, 'cache_plugins', )
'CallbackModule', 'ansible.plugins.callback', C.DEFAULT_CALLBACK_PLUGIN_PATH, 'callback_plugins', )
'Connection', 'ansible.plugins.connection', C.DEFAULT_CONNECTION_PLUGIN_PATH, 'connection_plugins', aliases={'paramiko': 'paramiko_ssh'}, required_base_class='ConnectionBase', )
'ShellModule', 'ansible.plugins.shell', 'shell_plugins', 'shell_plugins', )
'', 'ansible.modules', C.DEFAULT_MODULE_PATH, 'library', )
'', 'ansible.module_utils', C.DEFAULT_MODULE_UTILS_PATH, 'module_utils', )
# NB: dedicated loader is currently necessary because PS module_utils expects "with subdir" lookup where # regular module_utils doesn't. This can be revisited once we have more granular loaders. '', 'ansible.module_utils', C.DEFAULT_MODULE_UTILS_PATH, 'module_utils', )
'LookupModule', 'ansible.plugins.lookup', C.DEFAULT_LOOKUP_PLUGIN_PATH, 'lookup_plugins', required_base_class='LookupBase', )
'FilterModule', 'ansible.plugins.filter', C.DEFAULT_FILTER_PLUGIN_PATH, 'filter_plugins', )
'TestModule', 'ansible.plugins.test', C.DEFAULT_TEST_PLUGIN_PATH, 'test_plugins' )
'StrategyModule', 'ansible.plugins.strategy', C.DEFAULT_STRATEGY_PLUGIN_PATH, 'strategy_plugins', required_base_class='StrategyBase', )
'TerminalModule', 'ansible.plugins.terminal', 'terminal_plugins', 'terminal_plugins' )
'VarsModule', 'ansible.plugins.vars', C.DEFAULT_VARS_PLUGIN_PATH, 'vars_plugins', )
'Cliconf', 'ansible.plugins.cliconf', 'cliconf_plugins', 'cliconf_plugins', required_base_class='CliconfBase' )
'Netconf', 'ansible.plugins.netconf', 'netconf_plugins', 'netconf_plugins', required_base_class='NetconfBase' )
'InventoryModule', 'ansible.plugins.inventory', C.DEFAULT_INVENTORY_PLUGIN_PATH, 'inventory_plugins' ) |