2012-11-02 00:41:50 +01:00
|
|
|
# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com>
|
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
|
|
|
|
import os
|
2013-06-21 01:34:47 +02:00
|
|
|
import os.path
|
2012-11-02 00:41:50 +01:00
|
|
|
import sys
|
|
|
|
import glob
|
|
|
|
import imp
|
2013-07-03 22:47:20 +02:00
|
|
|
from ansible import constants as C
|
2012-11-02 00:41:50 +01:00
|
|
|
from ansible import errors
|
|
|
|
|
2013-04-20 15:09:35 +02:00
|
|
|
MODULE_CACHE = {}
|
2013-04-20 18:31:14 +02:00
|
|
|
PATH_CACHE = {}
|
|
|
|
PLUGIN_PATH_CACHE = {}
|
2012-11-02 00:41:50 +01:00
|
|
|
_basedirs = []
|
|
|
|
|
|
|
|
def push_basedir(basedir):
|
2013-08-12 22:17:53 +02:00
|
|
|
# avoid pushing the same absolute dir more than once
|
2014-01-21 19:41:58 +01:00
|
|
|
basedir = os.path.realpath(basedir)
|
2013-06-05 23:25:24 +02:00
|
|
|
if basedir not in _basedirs:
|
|
|
|
_basedirs.insert(0, basedir)
|
2012-11-02 00:41:50 +01:00
|
|
|
|
|
|
|
class PluginLoader(object):
|
2013-04-20 18:31:14 +02:00
|
|
|
|
|
|
|
'''
|
|
|
|
PluginLoader loads plugins from the configured plugin directories.
|
2012-11-02 00:41:50 +01:00
|
|
|
|
|
|
|
It searches for plugins by iterating through the combined list of
|
2013-04-20 18:31:14 +02:00
|
|
|
play basedirs, configured paths, and the python path.
|
2012-11-02 00:41:50 +01:00
|
|
|
The first match is used.
|
2013-04-20 18:31:14 +02:00
|
|
|
'''
|
|
|
|
|
2012-11-02 00:41:50 +01:00
|
|
|
def __init__(self, class_name, package, config, subdir, aliases={}):
|
2013-04-20 18:31:14 +02:00
|
|
|
|
|
|
|
self.class_name = class_name
|
|
|
|
self.package = package
|
|
|
|
self.config = config
|
|
|
|
self.subdir = subdir
|
|
|
|
self.aliases = aliases
|
|
|
|
|
2013-04-20 15:09:35 +02:00
|
|
|
if not class_name in MODULE_CACHE:
|
|
|
|
MODULE_CACHE[class_name] = {}
|
2013-04-20 18:31:14 +02:00
|
|
|
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]
|
2013-04-20 15:09:35 +02:00
|
|
|
|
2012-11-18 18:40:56 +01:00
|
|
|
self._extra_dirs = []
|
2015-02-17 22:24:46 +01:00
|
|
|
self._searched_paths = set()
|
2012-11-02 00:41:50 +01:00
|
|
|
|
2013-04-20 22:07:06 +02:00
|
|
|
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)
|
|
|
|
|
2014-09-26 17:25:56 +02:00
|
|
|
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
|
|
|
|
|
2013-04-28 21:22:46 +02:00
|
|
|
def _get_package_paths(self):
|
2013-04-20 18:31:14 +02:00
|
|
|
''' Gets the path of a Python package '''
|
|
|
|
|
2013-04-28 21:22:46 +02:00
|
|
|
paths = []
|
2012-11-18 18:37:30 +01:00
|
|
|
if not self.package:
|
|
|
|
return []
|
2012-11-02 00:41:50 +01:00
|
|
|
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)
|
2014-10-05 04:40:59 +02:00
|
|
|
paths.extend(self._all_directories(self.package_path))
|
|
|
|
return paths
|
2012-11-02 00:41:50 +01:00
|
|
|
|
|
|
|
def _get_paths(self):
|
2013-04-20 18:31:14 +02:00
|
|
|
''' Return a list of paths to search for plugins in '''
|
|
|
|
|
|
|
|
if self._paths is not None:
|
|
|
|
return self._paths
|
2012-11-02 00:41:50 +01:00
|
|
|
|
2014-10-05 04:40:59 +02:00
|
|
|
ret = self._extra_dirs[:]
|
2012-12-20 14:17:12 +01:00
|
|
|
for basedir in _basedirs:
|
2014-01-21 19:41:58 +01:00
|
|
|
fullpath = os.path.realpath(os.path.join(basedir, self.subdir))
|
2013-04-28 21:03:45 +02:00
|
|
|
if os.path.isdir(fullpath):
|
2014-09-26 16:55:00 +02:00
|
|
|
|
2013-04-28 21:03:45 +02:00
|
|
|
files = glob.glob("%s/*" % fullpath)
|
2014-09-26 16:55:00 +02:00
|
|
|
|
|
|
|
# allow directories to be two levels deep
|
|
|
|
files2 = glob.glob("%s/*/*" % fullpath)
|
|
|
|
|
2014-09-26 17:25:56 +02:00
|
|
|
if files2 is not None:
|
|
|
|
files.extend(files2)
|
2014-09-26 16:55:00 +02:00
|
|
|
|
2013-04-28 21:03:45 +02:00
|
|
|
for file in files:
|
2013-04-28 21:22:46 +02:00
|
|
|
if os.path.isdir(file) and file not in ret:
|
2013-05-25 00:05:28 +02:00
|
|
|
ret.append(file)
|
2013-04-28 21:03:45 +02:00
|
|
|
if fullpath not in ret:
|
|
|
|
ret.append(fullpath)
|
2013-05-25 00:05:28 +02:00
|
|
|
|
2014-07-03 03:02:28 +02:00
|
|
|
# look in any configured plugin paths, allow one level deep for subcategories
|
2014-09-28 18:09:53 +02:00
|
|
|
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))
|
2014-11-10 17:52:23 +01:00
|
|
|
contents = glob.glob("%s/*" % path) + glob.glob("%s/*/*" % path)
|
2014-09-28 18:09:53 +02:00
|
|
|
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)
|
2013-04-28 21:22:46 +02:00
|
|
|
|
|
|
|
# look for any plugins installed in the package subtree
|
|
|
|
ret.extend(self._get_package_paths())
|
2013-04-20 18:31:14 +02:00
|
|
|
|
2014-10-05 04:40:59 +02:00
|
|
|
# cache and return the result
|
2013-04-20 18:31:14 +02:00
|
|
|
self._paths = ret
|
2012-12-20 14:17:12 +01:00
|
|
|
return ret
|
2012-11-18 18:40:56 +01:00
|
|
|
|
2013-04-20 18:31:14 +02:00
|
|
|
|
2013-03-01 23:22:07 +01:00
|
|
|
def add_directory(self, directory, with_subdir=False):
|
2013-04-20 18:31:14 +02:00
|
|
|
''' Adds an additional directory to the search path '''
|
|
|
|
|
2014-01-21 19:41:58 +01:00
|
|
|
directory = os.path.realpath(directory)
|
2013-04-20 18:31:14 +02:00
|
|
|
|
2013-01-23 19:55:12 +01:00
|
|
|
if directory is not None:
|
2013-03-01 23:22:07 +01:00
|
|
|
if with_subdir:
|
|
|
|
directory = os.path.join(directory, self.subdir)
|
2013-07-28 18:47:26 +02:00
|
|
|
if directory not in self._extra_dirs:
|
2014-10-05 04:40:59 +02:00
|
|
|
# append the directory and invalidate the path cache
|
2013-07-28 18:47:26 +02:00
|
|
|
self._extra_dirs.append(directory)
|
2014-10-05 04:40:59 +02:00
|
|
|
self._paths = None
|
2012-11-02 00:41:50 +01:00
|
|
|
|
2014-10-08 17:53:06 +02:00
|
|
|
def find_plugin(self, name, suffixes=None):
|
2013-04-20 18:31:14 +02:00
|
|
|
''' Find a plugin named name '''
|
|
|
|
|
2014-06-17 22:15:18 +02:00
|
|
|
if not suffixes:
|
|
|
|
if self.class_name:
|
|
|
|
suffixes = ['.py']
|
|
|
|
else:
|
2014-10-08 17:53:06 +02:00
|
|
|
suffixes = ['.py', '']
|
2013-04-20 18:31:14 +02:00
|
|
|
|
2015-02-17 22:24:46 +01:00
|
|
|
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]:
|
2015-02-18 12:07:35 +01:00
|
|
|
if os.path.isdir(path):
|
2015-02-27 01:01:42 +01:00
|
|
|
full_paths = (os.path.join(path, f) for f in os.listdir(path))
|
|
|
|
for full_path in (f for f in full_paths if os.path.isfile(f)):
|
2015-02-18 12:07:35 +01:00
|
|
|
for suffix in suffixes:
|
2015-02-27 01:01:42 +01:00
|
|
|
if full_path.endswith(suffix):
|
2015-02-18 12:07:35 +01:00
|
|
|
full_name = os.path.basename(full_path)
|
|
|
|
break
|
|
|
|
else: # Yes, this is a for-else: http://bit.ly/1ElPkyg
|
|
|
|
continue
|
2015-02-27 01:01:42 +01:00
|
|
|
|
2015-02-18 12:07:35 +01:00
|
|
|
if full_name not in self._plugin_path_cache:
|
|
|
|
self._plugin_path_cache[full_name] = full_path
|
2015-02-27 01:01:42 +01:00
|
|
|
|
2015-02-17 22:24:46 +01:00
|
|
|
self._searched_paths.add(path)
|
|
|
|
for full_name in potential_names:
|
2014-12-01 23:36:57 +01:00
|
|
|
if full_name in self._plugin_path_cache:
|
|
|
|
return self._plugin_path_cache[full_name]
|
2013-04-20 18:31:14 +02:00
|
|
|
|
2014-12-01 23:36:57 +01:00
|
|
|
# if nothing is found, try finding alias/deprecated
|
2014-10-27 23:52:56 +01:00
|
|
|
if not name.startswith('_'):
|
2015-02-17 22:24:46 +01:00
|
|
|
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]
|
2014-10-27 23:52:56 +01:00
|
|
|
|
2012-11-02 00:41:50 +01:00
|
|
|
return None
|
|
|
|
|
|
|
|
def has_plugin(self, name):
|
2013-04-20 18:31:14 +02:00
|
|
|
''' Checks if a plugin named name exists '''
|
|
|
|
|
2012-11-02 00:41:50 +01:00
|
|
|
return self.find_plugin(name) is not None
|
2013-04-20 18:31:14 +02:00
|
|
|
|
2012-11-02 00:41:50 +01:00
|
|
|
__contains__ = has_plugin
|
|
|
|
|
|
|
|
def get(self, name, *args, **kwargs):
|
2013-04-20 18:31:14 +02:00
|
|
|
''' instantiates a plugin of the given name using arguments '''
|
|
|
|
|
2012-11-02 00:41:50 +01:00
|
|
|
if name in self.aliases:
|
|
|
|
name = self.aliases[name]
|
|
|
|
path = self.find_plugin(name)
|
|
|
|
if path is None:
|
|
|
|
return None
|
|
|
|
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):
|
2014-07-03 03:02:28 +02:00
|
|
|
''' instantiates all plugins with the same arguments '''
|
2013-04-20 18:31:14 +02:00
|
|
|
|
2012-11-02 00:41:50 +01:00
|
|
|
for i in self._get_paths():
|
2013-08-20 01:48:17 +02:00
|
|
|
matches = glob.glob(os.path.join(i, "*.py"))
|
|
|
|
matches.sort()
|
|
|
|
for path in matches:
|
2012-11-02 00:41:50 +01:00
|
|
|
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)
|
|
|
|
yield getattr(self._module_cache[path], self.class_name)(*args, **kwargs)
|
|
|
|
|
2013-04-20 15:09:35 +02:00
|
|
|
action_loader = PluginLoader(
|
2014-07-03 03:02:28 +02:00
|
|
|
'ActionModule',
|
2013-04-20 15:09:35 +02:00
|
|
|
'ansible.runner.action_plugins',
|
|
|
|
C.DEFAULT_ACTION_PLUGIN_PATH,
|
|
|
|
'action_plugins'
|
|
|
|
)
|
|
|
|
|
2014-07-03 03:02:28 +02:00
|
|
|
cache_loader = PluginLoader(
|
|
|
|
'CacheModule',
|
|
|
|
'ansible.cache',
|
|
|
|
C.DEFAULT_CACHE_PLUGIN_PATH,
|
|
|
|
'cache_plugins'
|
|
|
|
)
|
|
|
|
|
2013-04-20 15:09:35 +02:00
|
|
|
callback_loader = PluginLoader(
|
2014-07-03 03:02:28 +02:00
|
|
|
'CallbackModule',
|
|
|
|
'ansible.callback_plugins',
|
|
|
|
C.DEFAULT_CALLBACK_PLUGIN_PATH,
|
2013-04-20 15:09:35 +02:00
|
|
|
'callback_plugins'
|
|
|
|
)
|
|
|
|
|
|
|
|
connection_loader = PluginLoader(
|
2014-07-03 03:02:28 +02:00
|
|
|
'Connection',
|
|
|
|
'ansible.runner.connection_plugins',
|
|
|
|
C.DEFAULT_CONNECTION_PLUGIN_PATH,
|
|
|
|
'connection_plugins',
|
2013-04-20 15:09:35 +02:00
|
|
|
aliases={'paramiko': 'paramiko_ssh'}
|
|
|
|
)
|
|
|
|
|
2014-06-17 22:15:18 +02:00
|
|
|
shell_loader = PluginLoader(
|
|
|
|
'ShellModule',
|
|
|
|
'ansible.runner.shell_plugins',
|
|
|
|
'shell_plugins',
|
|
|
|
'shell_plugins',
|
|
|
|
)
|
|
|
|
|
2013-04-20 15:09:35 +02:00
|
|
|
module_finder = PluginLoader(
|
2014-07-03 03:02:28 +02:00
|
|
|
'',
|
2014-09-26 16:55:00 +02:00
|
|
|
'ansible.modules',
|
2014-07-03 03:02:28 +02:00
|
|
|
C.DEFAULT_MODULE_PATH,
|
2013-04-20 15:09:35 +02:00
|
|
|
'library'
|
|
|
|
)
|
|
|
|
|
|
|
|
lookup_loader = PluginLoader(
|
2014-07-03 03:02:28 +02:00
|
|
|
'LookupModule',
|
|
|
|
'ansible.runner.lookup_plugins',
|
|
|
|
C.DEFAULT_LOOKUP_PLUGIN_PATH,
|
2013-04-20 15:09:35 +02:00
|
|
|
'lookup_plugins'
|
|
|
|
)
|
|
|
|
|
|
|
|
vars_loader = PluginLoader(
|
2014-07-03 03:02:28 +02:00
|
|
|
'VarsModule',
|
|
|
|
'ansible.inventory.vars_plugins',
|
|
|
|
C.DEFAULT_VARS_PLUGIN_PATH,
|
2013-04-20 15:09:35 +02:00
|
|
|
'vars_plugins'
|
|
|
|
)
|
|
|
|
|
|
|
|
filter_loader = PluginLoader(
|
2014-07-03 03:02:28 +02:00
|
|
|
'FilterModule',
|
|
|
|
'ansible.runner.filter_plugins',
|
|
|
|
C.DEFAULT_FILTER_PLUGIN_PATH,
|
2013-04-20 15:09:35 +02:00
|
|
|
'filter_plugins'
|
|
|
|
)
|
|
|
|
|
2014-11-27 01:58:45 +01:00
|
|
|
test_loader = PluginLoader(
|
|
|
|
'TestModule',
|
|
|
|
'ansible.runner.test_plugins',
|
|
|
|
C.DEFAULT_TEST_PLUGIN_PATH,
|
|
|
|
'test_plugins'
|
|
|
|
)
|
|
|
|
|
2014-02-25 03:48:15 +01:00
|
|
|
fragment_loader = PluginLoader(
|
|
|
|
'ModuleDocFragment',
|
|
|
|
'ansible.utils.module_docs_fragments',
|
|
|
|
os.path.join(os.path.dirname(__file__), 'module_docs_fragments'),
|
|
|
|
'',
|
|
|
|
)
|