Create a plugin loader system

pull/1648/head
Daniel Hokka Zakrisson 12 years ago
parent 000d3832cc
commit e05e514861

@ -24,14 +24,6 @@ import os.path
from ansible.color import stringc from ansible.color import stringc
import ansible.constants as C import ansible.constants as C
dirname = os.path.dirname(__file__)
callbacks = utils.import_plugins(os.path.join(dirname, 'callback_plugins'))
callbacks = [ c.CallbackModule() for c in callbacks.values() ]
def load_more_callbacks(dirname):
callbacks.extend([c.CallbackModule() for c in utils.import_plugins(dirname).values()])
for i in C.DEFAULT_CALLBACK_PLUGIN_PATH.split(os.pathsep):
load_more_callbacks(i)
cowsay = None cowsay = None
if os.getenv("ANSIBLE_NOCOWS") is not None: if os.getenv("ANSIBLE_NOCOWS") is not None:
cowsay = None cowsay = None
@ -45,7 +37,7 @@ elif os.path.exists("/usr/local/bin/cowsay"):
def call_callback_module(method_name, *args, **kwargs): def call_callback_module(method_name, *args, **kwargs):
for callback_plugin in callbacks: for callback_plugin in utils.plugins.callback_loader.all():
methods = [ methods = [
getattr(callback_plugin, method_name, None), getattr(callback_plugin, method_name, None),
getattr(callback_plugin, 'on_any', None) getattr(callback_plugin, 'on_any', None)

@ -29,20 +29,13 @@ from ansible.inventory.host import Host
from ansible import errors from ansible import errors
from ansible import utils from ansible import utils
# FIXME, adapt.
dirname = os.path.dirname(__file__)
vars_plugin_list = utils.import_plugins(os.path.join(dirname, 'vars_plugins'))
for i in reversed(C.DEFAULT_VARS_PLUGIN_PATH.split(os.pathsep)):
vars_plugin_list.update(utils.import_plugins(i))
class Inventory(object): class Inventory(object):
""" """
Host inventory for ansible. Host inventory for ansible.
""" """
__slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', '_is_script', __slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', '_is_script',
'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache', '_groups_list', 'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache', '_groups_list']
'_vars_plugins' ]
def __init__(self, host_list=C.DEFAULT_HOST_LIST): def __init__(self, host_list=C.DEFAULT_HOST_LIST):
@ -74,6 +67,9 @@ class Inventory(object):
host_list = host_list.split(",") host_list = host_list.split(",")
host_list = [ h for h in host_list if h and h.strip() ] host_list = [ h for h in host_list if h and h.strip() ]
else:
utils.plugins.push_basedir(self.basedir())
if type(host_list) == list: if type(host_list) == list:
all = Group('all') all = Group('all')
self.groups = [ all ] self.groups = [ all ]
@ -95,8 +91,6 @@ class Inventory(object):
else: else:
raise errors.AnsibleError("YAML inventory support is deprecated in 0.6 and removed in 0.7, see the migration script in examples/scripts in the git checkout") raise errors.AnsibleError("YAML inventory support is deprecated in 0.6 and removed in 0.7, see the migration script in examples/scripts in the git checkout")
self._vars_plugins = [ i.VarsModule(self) for i in vars_plugin_list.values() ]
def _match(self, str, pattern_str): def _match(self, str, pattern_str):
return fnmatch.fnmatch(str, pattern_str) return fnmatch.fnmatch(str, pattern_str)
@ -280,8 +274,7 @@ class Inventory(object):
raise errors.AnsibleError("host not found: %s" % hostname) raise errors.AnsibleError("host not found: %s" % hostname)
vars = {} vars = {}
for ip in self._vars_plugins: for updated in map(lambda x: x.run(host), utils.plugins.vars_loader.all(self)):
updated = ip.run(host)
if updated is not None: if updated is not None:
vars.update(updated) vars.update(updated)

@ -27,8 +27,6 @@ from play import Play
SETUP_CACHE = collections.defaultdict(dict) SETUP_CACHE = collections.defaultdict(dict)
plugins_dir = os.path.join(os.path.dirname(__file__), '..', 'runner')
class PlayBook(object): class PlayBook(object):
''' '''
runs an ansible playbook, given as a datastructure or YAML filename. runs an ansible playbook, given as a datastructure or YAML filename.
@ -112,7 +110,6 @@ class PlayBook(object):
self.inventory.subset(subset) self.inventory.subset(subset)
self.modules_list = utils.get_available_modules(self.module_path) self.modules_list = utils.get_available_modules(self.module_path)
self.lookup_plugins_list = ansible.runner.lookup_plugin_list
if not self.inventory._is_script: if not self.inventory._is_script:
self.global_vars.update(self.inventory.get_group_variables('all')) self.global_vars.update(self.inventory.get_group_variables('all'))
@ -120,9 +117,9 @@ class PlayBook(object):
self.basedir = os.path.dirname(playbook) self.basedir = os.path.dirname(playbook)
(self.playbook, self.play_basedirs) = self._load_playbook_from_file(playbook) (self.playbook, self.play_basedirs) = self._load_playbook_from_file(playbook)
self.module_path = self.module_path + os.pathsep + os.path.join(self.basedir, "library") self.module_path = self.module_path + os.pathsep + os.path.join(self.basedir, "library")
ansible.callbacks.load_more_callbacks(os.path.join(self.basedir, "callback_plugins"))
self.lookup_plugins_list.update(utils.import_plugins(os.path.join(self.basedir, 'lookup_plugins'))) for i in self.play_basedirs:
utils.plugins.push_basedir(i)
# ***************************************************** # *****************************************************

@ -109,10 +109,10 @@ class Play(object):
if not k.startswith("with_"): if not k.startswith("with_"):
continue continue
plugin_name = k[5:] plugin_name = k[5:]
if plugin_name not in self.playbook.lookup_plugins_list: if plugin_name not in utils.plugins.lookup_loader:
raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name)) raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name))
terms = utils.varReplaceWithItems(self.basedir, x[k], task_vars) terms = utils.varReplaceWithItems(self.basedir, x[k], task_vars)
items = self.playbook.lookup_plugins_list[plugin_name].LookupModule(basedir=self.basedir, runner=None).run(terms, inject=task_vars) items = utils.plugins.lookup_loader.get(plugin_name, basedir=self.basedir, runner=None).run(terms, inject=task_vars)
for item in items: for item in items:
mv = task_vars.copy() mv = task_vars.copy()

@ -51,7 +51,7 @@ class Task(object):
# code to allow "with_glob" and to reference a lookup plugin named glob # code to allow "with_glob" and to reference a lookup plugin named glob
elif x.startswith("with_"): elif x.startswith("with_"):
plugin_name = x.replace("with_","") plugin_name = x.replace("with_","")
if plugin_name in play.playbook.lookup_plugins_list: if plugin_name in utils.plugins.lookup_loader:
ds['items_lookup_plugin'] = plugin_name ds['items_lookup_plugin'] = plugin_name
ds['items_lookup_terms'] = ds[x] ds['items_lookup_terms'] = ds[x]
ds.pop(x) ds.pop(x)

@ -45,14 +45,6 @@ try:
except ImportError: except ImportError:
HAS_ATFORK=False HAS_ATFORK=False
dirname = os.path.dirname(__file__)
action_plugin_list = utils.import_plugins(os.path.join(dirname, 'action_plugins'))
for i in reversed(C.DEFAULT_ACTION_PLUGIN_PATH.split(os.pathsep)):
action_plugin_list.update(utils.import_plugins(i))
lookup_plugin_list = utils.import_plugins(os.path.join(dirname, 'lookup_plugins'))
for i in reversed(C.DEFAULT_LOOKUP_PLUGIN_PATH.split(os.pathsep)):
lookup_plugin_list.update(utils.import_plugins(i))
multiprocessing_runner = None multiprocessing_runner = None
################################################ ################################################
@ -164,19 +156,6 @@ class Runner(object):
# ensure we are using unique tmp paths # ensure we are using unique tmp paths
random.seed() random.seed()
# instantiate plugin classes
self.action_plugins = {}
self.lookup_plugins = {}
for (k,v) in action_plugin_list.iteritems():
self.action_plugins[k] = v.ActionModule(self)
for (k,v) in lookup_plugin_list.iteritems():
self.lookup_plugins[k] = v.LookupModule(runner=self, basedir=self.basedir)
for (k,v) in utils.import_plugins(os.path.join(self.basedir, 'action_plugins')).iteritems():
self.action_plugins[k] = v.ActionModule(self)
for (k,v) in utils.import_plugins(os.path.join(self.basedir, 'lookup_plugins')).iteritems():
self.lookup_plugins[k] = v.LookupModule(runner=self, basedir=self.basedir)
# ***************************************************** # *****************************************************
def _transfer_str(self, conn, tmp, name, data): def _transfer_str(self, conn, tmp, name, data):
@ -292,10 +271,10 @@ class Runner(object):
# allow with_foo to work in playbooks... # allow with_foo to work in playbooks...
items = None items = None
items_plugin = self.module_vars.get('items_lookup_plugin', None) items_plugin = self.module_vars.get('items_lookup_plugin', None)
if items_plugin is not None and items_plugin in self.lookup_plugins: if items_plugin is not None and items_plugin in utils.plugins.lookup_loader:
items_terms = self.module_vars.get('items_lookup_terms', '') items_terms = self.module_vars.get('items_lookup_terms', '')
items_terms = utils.varReplaceWithItems(self.basedir, items_terms, inject) items_terms = utils.varReplaceWithItems(self.basedir, items_terms, inject)
items = self.lookup_plugins[items_plugin].run(items_terms, inject=inject) items = utils.plugins.lookup_loader.get(items_plugin, runner=self, basedir=self.basedir).run(items_terms, inject=inject)
if type(items) != list: if type(items) != list:
raise errors.AnsibleError("lookup plugins have to return a list: %r" % items) raise errors.AnsibleError("lookup plugins have to return a list: %r" % items)
@ -417,16 +396,16 @@ class Runner(object):
tmp = self._make_tmp_path(conn) tmp = self._make_tmp_path(conn)
result = None result = None
handler = self.action_plugins.get(module_name, None) if module_name in utils.plugins.action_loader:
if handler:
if self.background != 0: if self.background != 0:
raise errors.AnsibleError("async mode is not supported with the %s module" % module_name) raise errors.AnsibleError("async mode is not supported with the %s module" % module_name)
handler = utils.plugins.action_loader.get(module_name, self)
result = handler.run(conn, tmp, module_name, module_args, inject) result = handler.run(conn, tmp, module_name, module_args, inject)
else: else:
if self.background == 0: if self.background == 0:
result = self.action_plugins['normal'].run(conn, tmp, module_name, module_args, inject) result = utils.plugins.action_loader.get('normal', self).run(conn, tmp, module_name, module_args, inject)
else: else:
result = self.action_plugins['async'].run(conn, tmp, module_name, module_args, inject) result = utils.plugins.action_loader.get('async', self).run(conn, tmp, module_name, module_args, inject)
conn.close() conn.close()
@ -648,7 +627,7 @@ class Runner(object):
# to be ran once per group of hosts. Example module: pause, # to be ran once per group of hosts. Example module: pause,
# run once per hostgroup, rather than pausing once per each # run once per hostgroup, rather than pausing once per each
# host. # host.
p = self.action_plugins.get(self.module_name, None) p = utils.plugins.action_loader.get(self.module_name, self)
if p and getattr(p, 'BYPASS_HOST_LOOP', None): if p and getattr(p, 'BYPASS_HOST_LOOP', None):
# Expose the current hostgroup to the bypassing plugins # Expose the current hostgroup to the bypassing plugins
self.host_set = hosts self.host_set = hosts

@ -24,32 +24,19 @@ import ansible.constants as C
import os import os
import os.path import os.path
dirname = os.path.dirname(__file__)
modules = utils.import_plugins(os.path.join(dirname, 'connection_plugins'))
for i in reversed(C.DEFAULT_CONNECTION_PLUGIN_PATH.split(os.pathsep)):
modules.update(utils.import_plugins(i))
# rename this module
modules['paramiko'] = modules['paramiko_ssh']
del modules['paramiko_ssh']
class Connection(object): class Connection(object):
''' Handles abstract connections to remote hosts ''' ''' Handles abstract connections to remote hosts '''
def __init__(self, runner): def __init__(self, runner):
self.runner = runner self.runner = runner
self.modules = None
def connect(self, host, port): def connect(self, host, port):
if self.modules is None:
self.modules = modules.copy()
self.modules.update(utils.import_plugins(os.path.join(self.runner.basedir, 'connection_plugins')))
conn = None conn = None
transport = self.runner.transport transport = self.runner.transport
module = self.modules.get(transport, None) conn = utils.plugins.connection_loader.get(transport, self.runner, host, port)
if module is None: if conn is None:
raise AnsibleError("unsupported connection type: %s" % transport) raise AnsibleError("unsupported connection type: %s" % transport)
conn = module.Connection(self.runner, host, port)
self.active = conn.connect() self.active = conn.connect()
return self.active return self.active

@ -24,11 +24,10 @@ import operator
from ansible import errors from ansible import errors
from ansible import __version__ from ansible import __version__
from ansible.utils.template import * from ansible.utils.template import *
from ansible.utils.plugins import *
import ansible.constants as C import ansible.constants as C
import time import time
import StringIO import StringIO
import imp
import glob
import stat import stat
import termios import termios
import tty import tty
@ -457,17 +456,6 @@ def filter_leading_non_json_lines(buf):
filtered_lines.write(line + '\n') filtered_lines.write(line + '\n')
return filtered_lines.getvalue() return filtered_lines.getvalue()
def import_plugins(directory):
modules = {}
python_files = os.path.join(directory, '*.py')
for path in glob.glob(python_files):
if path.startswith("_"):
continue
name, ext = os.path.splitext(os.path.basename(path))
if not name.startswith("_"):
modules[name] = imp.load_source(name, path)
return modules
def get_available_modules(dirname=None): def get_available_modules(dirname=None):
""" """
returns a list of modules available based on current directory returns a list of modules available based on current directory

@ -0,0 +1,97 @@
# (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
import sys
import glob
import imp
import ansible.constants as C
from ansible import errors
_basedirs = []
def push_basedir(basedir):
_basedirs.insert(0, basedir)
class PluginLoader(object):
"""PluginLoader loads plugins from the best source
It searches for plugins by iterating through the combined list of
play basedirs, configured paths, and the installed package directory.
The first match is used.
"""
def __init__(self, class_name, package, config, subdir, aliases={}):
"""Create a new PluginLoader"""
self.class_name = class_name
self.package = package
self.config = config
self.subdir = subdir
self.aliases = aliases
self._module_cache = {}
def _get_package_path(self):
"""Gets the path of a Python package"""
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)
return self.package_path
def _get_paths(self):
"""Return a list of paths to search for plugins in
The list is searched in order."""
return [os.path.join(basedir, self.subdir) for basedir in _basedirs] + self.config.split(os.pathsep) + [self._get_package_path()]
def find_plugin(self, name):
"""Find a plugin named name"""
for i in self._get_paths():
path = os.path.join(i, "%s.py" % name)
if os.path.exists(path):
return path
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):
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):
for i in self._get_paths():
for path in glob.glob(os.path.join(i, "*.py")):
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)
action_loader = PluginLoader('ActionModule', 'ansible.runner.action_plugins', C.DEFAULT_ACTION_PLUGIN_PATH, 'action_plugins')
callback_loader = PluginLoader('CallbackModule', 'ansible.callback_plugins', C.DEFAULT_CALLBACK_PLUGIN_PATH, 'callback_plugins')
connection_loader = PluginLoader('Connection', 'ansible.runner.connection_plugins', C.DEFAULT_CONNECTION_PLUGIN_PATH, 'connection_plugins', aliases={'paramiko': 'paramiko_ssh'})
lookup_loader = PluginLoader('LookupModule', 'ansible.runner.lookup_plugins', C.DEFAULT_LOOKUP_PLUGIN_PATH, 'lookup_plugins')
vars_loader = PluginLoader('VarsModule', 'ansible.inventory.vars_plugins', C.DEFAULT_VARS_PLUGIN_PATH, 'vars_plugins')
Loading…
Cancel
Save