diff --git a/lib/ansible/callbacks.py b/lib/ansible/callbacks.py
index cf204bbdb26..75299e7284c 100644
--- a/lib/ansible/callbacks.py
+++ b/lib/ansible/callbacks.py
@@ -45,10 +45,25 @@ if cowsay and noncow == 'random':
cows = out.split()
cows.append(False)
noncow = random.choice(cows)
+
+callback_plugins = [x for x in utils.plugins.callback_loader.all()]
+
+def set_play(callback, play):
+ ''' used to notify callback plugins of context '''
+ callback.play = play
+ for callback_plugin in callback_plugins:
+ callback_plugin.play = play
+
+def set_task(callback, task):
+ ''' used to notify callback plugins of context '''
+ callback.task = task
+ for callback_plugin in callback_plugins:
+ callback_plugin.task = task
+
def call_callback_module(method_name, *args, **kwargs):
- for callback_plugin in utils.plugins.callback_loader.all():
+ for callback_plugin in callback_plugins:
methods = [
getattr(callback_plugin, method_name, None),
getattr(callback_plugin, 'on_any', None)
diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py
index 2eb98fa05d5..856ec632071 100644
--- a/lib/ansible/playbook/__init__.py
+++ b/lib/ansible/playbook/__init__.py
@@ -16,8 +16,8 @@
# along with Ansible. If not, see .
import ansible.inventory
-import ansible.runner
import ansible.constants as C
+import ansible.runner
from ansible.utils import template
from ansible import utils
from ansible import errors
@@ -214,8 +214,9 @@ class PlayBook(object):
for (play_ds, play_basedir) in zip(self.playbook, self.play_basedirs):
play = Play(self, play_ds, play_basedir)
- self.callbacks.play = play
- self.runner_callbacks.play = play
+ assert play is not None
+ ansible.callbacks.set_play(self.callbacks, play)
+ ansible.callbacks.set_play(self.runner_callbacks, play)
matched_tags, unmatched_tags = play.compare_tags(self.only_tags)
matched_tags_all = matched_tags_all | matched_tags
@@ -317,8 +318,8 @@ class PlayBook(object):
def _run_task(self, play, task, is_handler):
''' run a single task in the playbook and recursively run any subtasks. '''
- self.callbacks.task = task
- self.runner_callbacks.task = task
+ ansible.callbacks.set_task(self.callbacks, task)
+ ansible.callbacks.set_task(self.runner_callbacks, task)
self.callbacks.on_task_start(template.template(play.basedir, task.name, task.module_vars, lookup_fatal=False), is_handler)
if hasattr(self.callbacks, 'skip_task') and self.callbacks.skip_task:
@@ -402,8 +403,8 @@ class PlayBook(object):
self.callbacks.on_setup()
self.inventory.restrict_to(host_list)
- self.callbacks.task = None
- self.runner_callbacks.task = None
+ ansible.callbacks.set_task(self.callbacks, None)
+ ansible.callbacks.set_task(self.runner_callbacks, None)
# push any variables down to the system
setup_results = ansible.runner.Runner(
diff --git a/lib/ansible/utils/plugins.py b/lib/ansible/utils/plugins.py
index 5a2867fd832..33ed750b79e 100644
--- a/lib/ansible/utils/plugins.py
+++ b/lib/ansible/utils/plugins.py
@@ -22,6 +22,7 @@ import imp
import ansible.constants as C
from ansible import errors
+MODULE_CACHE = {}
_basedirs = []
def push_basedir(basedir):
@@ -41,7 +42,11 @@ class PluginLoader(object):
self.config = config
self.subdir = subdir
self.aliases = aliases
- self._module_cache = {}
+
+ if not class_name in MODULE_CACHE:
+ MODULE_CACHE[class_name] = {}
+
+ self._module_cache = MODULE_CACHE[class_name]
self._extra_dirs = []
def _get_package_path(self):
@@ -111,6 +116,7 @@ class PluginLoader(object):
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))
@@ -120,10 +126,54 @@ class PluginLoader(object):
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'})
-module_finder = PluginLoader('', '', C.DEFAULT_MODULE_PATH, 'library')
-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')
-filter_loader = PluginLoader('FilterModule', 'ansible.runner.filter_plugins', C.DEFAULT_FILTER_PLUGIN_PATH, 'filter_plugins')
+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'}
+)
+
+module_finder = PluginLoader(
+ '',
+ '',
+ C.DEFAULT_MODULE_PATH,
+ 'library'
+)
+
+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'
+)
+
+filter_loader = PluginLoader(
+ 'FilterModule',
+ 'ansible.runner.filter_plugins',
+ C.DEFAULT_FILTER_PLUGIN_PATH,
+ 'filter_plugins'
+)
+
+
diff --git a/plugins/callbacks/context_demo.py b/plugins/callbacks/context_demo.py
new file mode 100644
index 00000000000..5c3015d85f6
--- /dev/null
+++ b/plugins/callbacks/context_demo.py
@@ -0,0 +1,31 @@
+# (C) 2012, Michael DeHaan,
+
+# 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 .
+
+import os
+import time
+import json
+
+class CallbackModule(object):
+ """
+ This is a very trivial example of how any callback function can get at play and task objects.
+ play will be 'None' for runner invocations, and task will be None for 'setup' invocations.
+ """
+
+ def on_any(self, *args, **kwargs):
+ play = getattr(self, 'play', None)
+ task = getattr(self, 'task', None)
+ print "play = %s, task = %s, args = %s, kwargs = %s" % (play,task,args,kwargs)