diff options
Diffstat (limited to 'lib/ansible/plugins/__init__.py')
-rw-r--r-- | lib/ansible/plugins/__init__.py | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/lib/ansible/plugins/__init__.py b/lib/ansible/plugins/__init__.py new file mode 100644 index 0000000000..5791677bd2 --- /dev/null +++ b/lib/ansible/plugins/__init__.py @@ -0,0 +1,334 @@ +# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> and others +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import glob +import imp +import inspect +import os +import os.path +import sys + +from ansible import constants as C +from ansible.utils.display import Display +from ansible import errors + +MODULE_CACHE = {} +PATH_CACHE = {} +PLUGIN_PATH_CACHE = {} +_basedirs = [] + +def push_basedir(basedir): + # avoid pushing the same absolute dir more than once + basedir = os.path.realpath(basedir) + if basedir not in _basedirs: + _basedirs.insert(0, basedir) + +def get_all_plugin_loaders(): + return [(name, obj) for (name, obj) in inspect.getmembers(sys.modules[__name__]) if isinstance(obj, PluginLoader)] + +class PluginLoader: + + ''' + 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. + ''' + + def __init__(self, class_name, package, config, subdir, aliases={}): + + self.class_name = class_name + self.package = package + self.config = config + self.subdir = subdir + self.aliases = aliases + + if not class_name in MODULE_CACHE: + MODULE_CACHE[class_name] = {} + if not class_name in PATH_CACHE: + PATH_CACHE[class_name] = None + if not class_name in PLUGIN_PATH_CACHE: + PLUGIN_PATH_CACHE[class_name] = {} + + self._module_cache = MODULE_CACHE[class_name] + self._paths = PATH_CACHE[class_name] + self._plugin_path_cache = PLUGIN_PATH_CACHE[class_name] + + self._extra_dirs = [] + self._searched_paths = set() + + def print_paths(self): + ''' Returns a string suitable for printing of the search path ''' + + # Uses a list to get the order right + ret = [] + for i in self._get_paths(): + if i not in ret: + ret.append(i) + return os.pathsep.join(ret) + + def _all_directories(self, dir): + results = [] + results.append(dir) + for root, subdirs, files in os.walk(dir): + if '__init__.py' in files: + for x in subdirs: + results.append(os.path.join(root,x)) + return results + + def _get_package_paths(self): + ''' Gets the path of a Python package ''' + + paths = [] + if not self.package: + return [] + if not hasattr(self, 'package_path'): + m = __import__(self.package) + parts = self.package.split('.')[1:] + self.package_path = os.path.join(os.path.dirname(m.__file__), *parts) + paths.extend(self._all_directories(self.package_path)) + return paths + + def _get_paths(self): + ''' Return a list of paths to search for plugins in ''' + + if self._paths is not None: + return self._paths + + ret = self._extra_dirs[:] + for basedir in _basedirs: + fullpath = os.path.realpath(os.path.join(basedir, self.subdir)) + if os.path.isdir(fullpath): + files = glob.glob("%s/*" % fullpath) + + # allow directories to be two levels deep + files2 = glob.glob("%s/*/*" % fullpath) + + if files2 is not None: + files.extend(files2) + + for file in files: + if os.path.isdir(file) and file not in ret: + ret.append(file) + if fullpath not in ret: + ret.append(fullpath) + + # look in any configured plugin paths, allow one level deep for subcategories + if self.config is not None: + configured_paths = self.config.split(os.pathsep) + for path in configured_paths: + path = os.path.realpath(os.path.expanduser(path)) + contents = glob.glob("%s/*" % path) + glob.glob("%s/*/*" % path) + for c in contents: + if os.path.isdir(c) and c not in ret: + ret.append(c) + if path not in ret: + ret.append(path) + + # look for any plugins installed in the package subtree + ret.extend(self._get_package_paths()) + + # cache and return the result + self._paths = ret + return ret + + + def add_directory(self, directory, with_subdir=False): + ''' 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 + + def find_plugin(self, name, suffixes=None): + ''' Find a plugin named name ''' + + if not suffixes: + if self.class_name: + suffixes = ['.py'] + else: + suffixes = ['.py', ''] + + potential_names = frozenset('%s%s' % (name, s) for s in suffixes) + for full_name in potential_names: + if full_name in self._plugin_path_cache: + return self._plugin_path_cache[full_name] + + found = None + for path in [p for p in self._get_paths() if p not in self._searched_paths]: + if os.path.isdir(path): + try: + full_paths = (os.path.join(path, f) for f in os.listdir(path)) + except OSError as e: + d = Display() + d.warning("Error accessing plugin paths: %s" % str(e)) + for full_path in (f for f in full_paths if os.path.isfile(f)): + for suffix in suffixes: + if full_path.endswith(suffix): + full_name = os.path.basename(full_path) + break + else: # Yes, this is a for-else: http://bit.ly/1ElPkyg + continue + + if full_name not in self._plugin_path_cache: + self._plugin_path_cache[full_name] = full_path + + self._searched_paths.add(path) + for full_name in potential_names: + if full_name in self._plugin_path_cache: + return self._plugin_path_cache[full_name] + + # if nothing is found, try finding alias/deprecated + if not name.startswith('_'): + for alias_name in ('_%s' % n for n in potential_names): + # We've already cached all the paths at this point + if alias_name in self._plugin_path_cache: + return self._plugin_path_cache[alias_name] + + return None + + def has_plugin(self, name): + ''' Checks if a plugin named name exists ''' + + return self.find_plugin(name) is not None + + __contains__ = has_plugin + + def get(self, name, *args, **kwargs): + ''' instantiates a plugin of the given name using arguments ''' + + if name in self.aliases: + name = self.aliases[name] + path = self.find_plugin(name) + if path is None: + return None + elif kwargs.get('class_only', False): + return getattr(self._module_cache[path], self.class_name) + + if path not in self._module_cache: + self._module_cache[path] = imp.load_source('.'.join([self.package, name]), path) + return getattr(self._module_cache[path], self.class_name)(*args, **kwargs) + + def all(self, *args, **kwargs): + ''' instantiates all plugins with the same arguments ''' + + for i in self._get_paths(): + matches = glob.glob(os.path.join(i, "*.py")) + matches.sort() + for path in matches: + name, ext = os.path.splitext(os.path.basename(path)) + if name.startswith("_"): + continue + if path not in self._module_cache: + self._module_cache[path] = imp.load_source('.'.join([self.package, name]), path) + if kwargs.get('class_only', False): + obj = getattr(self._module_cache[path], self.class_name) + else: + obj = getattr(self._module_cache[path], self.class_name)(*args, **kwargs) + # set extra info on the module, in case we want it later + setattr(obj, '_original_path', path) + yield obj + +action_loader = PluginLoader( + 'ActionModule', + 'ansible.plugins.action', + C.DEFAULT_ACTION_PLUGIN_PATH, + 'action_plugins' +) + +cache_loader = PluginLoader( + 'CacheModule', + 'ansible.plugins.cache', + C.DEFAULT_CACHE_PLUGIN_PATH, + 'cache_plugins' +) + +callback_loader = PluginLoader( + 'CallbackModule', + 'ansible.plugins.callback', + C.DEFAULT_CALLBACK_PLUGIN_PATH, + 'callback_plugins' +) + +connection_loader = PluginLoader( + 'Connection', + 'ansible.plugins.connections', + C.DEFAULT_CONNECTION_PLUGIN_PATH, + 'connection_plugins', + aliases={'paramiko': 'paramiko_ssh'} +) + +shell_loader = PluginLoader( + 'ShellModule', + 'ansible.plugins.shell', + 'shell_plugins', + 'shell_plugins', +) + +module_loader = PluginLoader( + '', + 'ansible.modules', + C.DEFAULT_MODULE_PATH, + 'library' +) + +lookup_loader = PluginLoader( + 'LookupModule', + 'ansible.plugins.lookup', + C.DEFAULT_LOOKUP_PLUGIN_PATH, + 'lookup_plugins' +) + +vars_loader = PluginLoader( + 'VarsModule', + 'ansible.plugins.vars', + C.DEFAULT_VARS_PLUGIN_PATH, + 'vars_plugins' +) + +filter_loader = PluginLoader( + 'FilterModule', + 'ansible.plugins.filter', + C.DEFAULT_FILTER_PLUGIN_PATH, + 'filter_plugins' +) + +fragment_loader = PluginLoader( + 'ModuleDocFragment', + 'ansible.utils.module_docs_fragments', + os.path.join(os.path.dirname(__file__), 'module_docs_fragments'), + '', +) + +strategy_loader = PluginLoader( + 'StrategyModule', + 'ansible.plugins.strategies', + None, + 'strategy_plugins', +) |