From 53ac0bbec2d45a5f71ae89176f33521347066688 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 20 Apr 2013 09:09:35 -0400 Subject: [PATCH] Instantiate callback plugins only once so we can set play/task objects on them and they'll stick. --- lib/ansible/callbacks.py | 17 +++++++- lib/ansible/playbook/__init__.py | 15 +++---- lib/ansible/utils/plugins.py | 66 +++++++++++++++++++++++++++---- plugins/callbacks/context_demo.py | 31 +++++++++++++++ 4 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 plugins/callbacks/context_demo.py 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)