diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ansible/playbook/__init__.py | 9 | ||||
-rw-r--r-- | lib/ansible/playbook/task.py | 32 | ||||
-rw-r--r-- | lib/ansible/runner/__init__.py | 34 | ||||
-rw-r--r-- | lib/ansible/runner/action_plugins/copy.py | 13 | ||||
-rw-r--r-- | lib/ansible/runner/action_plugins/template.py | 5 | ||||
-rw-r--r-- | lib/ansible/runner/lookup_plugins/__init__.py | 0 | ||||
-rw-r--r-- | lib/ansible/runner/lookup_plugins/fileglob.py | 30 | ||||
-rw-r--r-- | lib/ansible/utils.py | 8 |
8 files changed, 112 insertions, 19 deletions
diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index 5616f3fb97..7ec0cb5451 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -26,6 +26,8 @@ from play import Play SETUP_CACHE = collections.defaultdict(dict) +plugins_dir = os.path.join(os.path.dirname(__file__), '..', 'runner') + class PlayBook(object): ''' runs an ansible playbook, given as a datastructure or YAML filename. @@ -105,9 +107,12 @@ class PlayBook(object): self.private_key_file = private_key_file self.only_tags = only_tags - self.inventory = ansible.inventory.Inventory(host_list) + self.inventory = ansible.inventory.Inventory(host_list) self.inventory.subset(subset) - self.modules_list = utils.get_available_modules(self.module_path) + + self.modules_list = utils.get_available_modules(self.module_path) + lookup_plugins_dir = os.path.join(plugins_dir, 'lookup_plugins') + self.lookup_plugins_list = utils.import_plugins(lookup_plugins_dir) if not self.inventory._is_script: self.global_vars.update(self.inventory.get_group_variables('all')) diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index 0b6d882196..e9e15e991f 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -26,7 +26,8 @@ class Task(object): 'notify', 'module_name', 'module_args', 'module_vars', 'play', 'notified_by', 'tags', 'register', 'with_items', 'delegate_to', 'first_available_file', 'ignore_errors', - 'local_action', 'transport', 'sudo', 'sudo_user', 'sudo_pass' + 'local_action', 'transport', 'sudo', 'sudo_user', 'sudo_pass', + 'items_lookup_plugin', 'items_lookup_terms' ] # to prevent typos and such @@ -40,11 +41,23 @@ class Task(object): def __init__(self, play, ds, module_vars=None): ''' constructor loads from a task or handler datastructure ''' - # code to allow for saying "modulename: args" versus "action: modulename args" for x in ds.keys(): + + # code to allow for saying "modulename: args" versus "action: modulename args" if x in play.playbook.modules_list: - ds['action'] = x + " " + ds.get(x, None) + ds['action'] = x + " " + ds[x] ds.pop(x) + + # code to allow "with_glob" and to reference a lookup plugin named glob + elif x.startswith("with_") and x != 'with_items': + plugin_name = x.replace("with_","") + if plugin_name in play.playbook.lookup_plugins_list: + ds['items_lookup_plugin'] = plugin_name + ds['items_lookup_terms'] = ds[x] + ds.pop(x) + else: + raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name)) + elif not x in Task.VALID_KEYS: raise errors.AnsibleError("%s is not a legal parameter in an Ansible task or handler" % x) @@ -101,6 +114,10 @@ class Task(object): self.first_available_file = ds.get('first_available_file', None) self.with_items = ds.get('with_items', None) + self.items_lookup_plugin = ds.get('items_lookup_plugin', None) + self.items_lookup_terms = ds.get('items_lookup_terms', None) + + self.ignore_errors = ds.get('ignore_errors', False) # notify can be a string or a list, store as a list @@ -125,8 +142,9 @@ class Task(object): self.action = utils.template(None, self.action, self.module_vars) # handle mutually incompatible options - if self.with_items is not None and self.first_available_file is not None: - raise errors.AnsibleError("with_items and first_available_file are mutually incompatible in a single task") + incompatibles = [ x for x in [ self.with_items, self.first_available_file, self.items_lookup_plugin ] if x is not None ] + if len(incompatibles) > 1: + raise errors.AnsibleError("with_items, with_(plugin), and first_available_file are mutually incompatible in a single task") # make first_available_file accessable to Runner code if self.first_available_file: @@ -137,6 +155,10 @@ class Task(object): self.with_items = [ ] self.module_vars['items'] = self.with_items + if self.items_lookup_plugin is not None: + self.module_vars['items_lookup_plugin'] = self.items_lookup_plugin + self.module_vars['items_lookup_terms'] = self.items_lookup_terms + # allow runner to see delegate_to option self.module_vars['delegate_to'] = self.delegate_to diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 2b56e9df53..3382fe013f 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -47,7 +47,7 @@ except ImportError: dirname = os.path.dirname(__file__) action_plugin_list = utils.import_plugins(os.path.join(dirname, 'action_plugins')) - +lookup_plugin_list = utils.import_plugins(os.path.join(dirname, 'lookup_plugins')) ################################################ @@ -71,8 +71,8 @@ def _executor_hook(job_queue, result_queue): traceback.print_exc() class HostVars(dict): - ''' A special view of setup_cache that adds values from the inventory - when needed. ''' + ''' A special view of setup_cache that adds values from the inventory when needed. ''' + def __init__(self, setup_cache, inventory): self.setup_cache = setup_cache self.inventory = inventory @@ -82,9 +82,10 @@ class HostVars(dict): def __getitem__(self, host): if not host in self.lookup: - self.lookup[host] = self.inventory.get_variables(host) - self.setup_cache[host].update(self.lookup[host]) - return self.setup_cache[host] + result = self.inventory.get_variables(host) + result.update(self.setup_cache.get(host, {})) + self.lookup[host] = result + return self.lookup[host] class Runner(object): ''' core API interface to ansible ''' @@ -160,8 +161,11 @@ class Runner(object): # 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(self) # ***************************************************** @@ -189,7 +193,10 @@ class Runner(object): afd, afile = tempfile.mkstemp() afo = os.fdopen(afd, 'w') - afo.write(data.encode('utf8')) + try: + afo.write(data.encode('utf8')) + except: + raise errors.AnsibleError("failure encoding into utf-8") afo.flush() afo.close() @@ -283,10 +290,18 @@ class Runner(object): # allow with_items to work in playbooks... # apt and yum are converted into a single call, others run in a loop - items = self.module_vars.get('items', []) if isinstance(items, basestring) and items.startswith("$"): items = utils.varReplaceWithItems(self.basedir, items, inject) + + # if we instead said 'with_foo' and there is a lookup module named foo... + items_plugin = self.module_vars.get('items_lookup_plugin', None) + if items_plugin is not None: + items_terms = self.module_vars.get('items_lookup_terms', '') + if items_plugin in self.lookup_plugins: + items_terms = utils.template(self.basedir, items_terms, inject) + items = self.lookup_plugins[items_plugin].run(items_terms) + if type(items) != list: raise errors.AnsibleError("with_items only takes a list: %s" % items) @@ -313,6 +328,7 @@ class Runner(object): results.append(result.result) if result.comm_ok == False: all_comm_ok = False + all_failed = True break for x in results: if x.get('changed') == True: @@ -320,7 +336,7 @@ class Runner(object): if (x.get('failed') == True) or (('rc' in x) and (x['rc'] != 0)): all_failed = True break - msg = 'All items succeeded' + msg = 'All items completed' if all_failed: msg = "One or more items failed." rd_result = dict(failed=all_failed, changed=all_changed, results=results, msg=msg) diff --git a/lib/ansible/runner/action_plugins/copy.py b/lib/ansible/runner/action_plugins/copy.py index c85de1442e..7718e9e933 100644 --- a/lib/ansible/runner/action_plugins/copy.py +++ b/lib/ansible/runner/action_plugins/copy.py @@ -39,6 +39,11 @@ class ActionModule(object): options = utils.parse_kv(module_args) source = options.get('src', None) dest = options.get('dest', None) + + if dest.endswith("/"): + base = os.path.basename(source) + dest = os.path.join(dest, base) + if (source is None and not 'first_available_file' in inject) or dest is None: result=dict(failed=True, msg="src and dest are required") return ReturnData(conn=conn, result=result) @@ -78,10 +83,16 @@ class ActionModule(object): # run the copy module module_args = "%s src=%s" % (module_args, tmp_src) + print "CALLING FILE WITH: %s" % module_args return self.runner._execute_module(conn, tmp, 'copy', module_args, inject=inject).daisychain('file', module_args) else: - # no need to transfer the file, already correct md5 + # no need to transfer the file, already correct md5, but still need to set src so the file module + # does not freak out. It's just the basename of the file. + + tmp_src = tmp + os.path.basename(source) + module_args = "%s src=%s" % (module_args, tmp_src) result = dict(changed=False, md5sum=remote_md5, transferred=False) + print "CALLING FILE WITH: %s" % module_args return ReturnData(conn=conn, result=result).daisychain('file', module_args) diff --git a/lib/ansible/runner/action_plugins/template.py b/lib/ansible/runner/action_plugins/template.py index 60fcba3f5a..446baed3aa 100644 --- a/lib/ansible/runner/action_plugins/template.py +++ b/lib/ansible/runner/action_plugins/template.py @@ -42,6 +42,11 @@ class ActionModule(object): options = utils.parse_kv(module_args) source = options.get('src', None) dest = options.get('dest', None) + + if dest.endswith("/"): + base = os.path.basename(source) + dest = os.path.join(dest, base) + if (source is None and 'first_available_file' not in inject) or dest is None: result = dict(failed=True, msg="src and dest are required") return ReturnData(conn=conn, comm_ok=False, result=result) diff --git a/lib/ansible/runner/lookup_plugins/__init__.py b/lib/ansible/runner/lookup_plugins/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/ansible/runner/lookup_plugins/__init__.py diff --git a/lib/ansible/runner/lookup_plugins/fileglob.py b/lib/ansible/runner/lookup_plugins/fileglob.py new file mode 100644 index 0000000000..82147ab3b3 --- /dev/null +++ b/lib/ansible/runner/lookup_plugins/fileglob.py @@ -0,0 +1,30 @@ +# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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 glob + +class LookupModule(object): + + def __init__(self, runner): + self.runner = runner + + def run(self, terms): + return [ f for f in glob.glob(terms) if os.path.isfile(f) ] + + + diff --git a/lib/ansible/utils.py b/lib/ansible/utils.py index 46bf53e339..f664114435 100644 --- a/lib/ansible/utils.py +++ b/lib/ansible/utils.py @@ -398,7 +398,10 @@ def template_from_file(basedir, path, vars): environment.filters['from_json'] = json.loads environment.filters['to_yaml'] = yaml.dump environment.filters['from_yaml'] = yaml.load - data = codecs.open(realpath, encoding="utf8").read() + try: + data = codecs.open(realpath, encoding="utf8").read() + except: + raise errors.AnsibleError("unable to process as utf-8: %s" % realpath) t = environment.from_string(data) vars = vars.copy() try: @@ -668,7 +671,8 @@ def filter_leading_non_json_lines(buf): def import_plugins(directory): modules = {} - for path in glob.glob(os.path.join(directory, '*.py')): + 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)) |