diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index 0368205d885..f26715dd93d 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -22,6 +22,7 @@ from ansible import utils from ansible import errors import ansible.callbacks import os +import shlex import collections from play import Play @@ -123,7 +124,7 @@ class PlayBook(object): # ***************************************************** - def _load_playbook_from_file(self, path): + def _load_playbook_from_file(self, path, vars={}): ''' run top level error checking on playbooks and allow them to include other playbooks. ''' @@ -135,21 +136,45 @@ class PlayBook(object): if type(playbook_data) != list: raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list") + basedir = os.path.dirname(path) for play in playbook_data: if type(play) != dict: raise errors.AnsibleError("parse error: each play in a playbook must a YAML dictionary (hash), recieved: %s" % play) if 'include' in play: - if len(play.keys()) == 1: - included_path = utils.path_dwim(self.basedir, play['include']) - (plays, basedirs) = self._load_playbook_from_file(included_path) - accumulated_plays.extend(plays) - play_basedirs.extend(basedirs) + if len(play.keys()) <= 2: + tokens = shlex.split(play['include']) + + items = [''] + for k in play.keys(): + if not k.startswith("with_"): + continue + plugin_name = k[5:] + 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)) + terms = utils.template_ds(basedir, play[k], vars) + items = utils.plugins.lookup_loader.get(plugin_name, basedir=basedir, runner=None).run(terms, inject=vars) + break + + for item in items: + incvars = vars.copy() + incvars['item'] = item + for t in tokens[1:]: + (k,v) = t.split("=", 1) + incvars[k] = utils.template_ds(basedir, v, incvars) + included_path = utils.path_dwim(basedir, tokens[0]) + (plays, basedirs) = self._load_playbook_from_file(included_path, incvars) + for p in plays: + if 'vars' not in p: + p['vars'] = {} + p['vars'].update(incvars) + accumulated_plays.extend(plays) + play_basedirs.extend(basedirs) else: - raise errors.AnsibleError("parse error: top level includes cannot be used with other directives: %s" % play) + raise errors.AnsibleError("parse error: playbook includes cannot be used with other directives: %s" % play) else: accumulated_plays.append(play) - play_basedirs.append(os.path.dirname(path)) + play_basedirs.append(basedir) return (accumulated_plays, play_basedirs) diff --git a/lib/ansible/playbook/play.py b/lib/ansible/playbook/play.py index 2aa7ff661ac..ebb8db26416 100644 --- a/lib/ansible/playbook/play.py +++ b/lib/ansible/playbook/play.py @@ -111,7 +111,7 @@ class Play(object): plugin_name = k[5:] 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)) - terms = utils.varReplaceWithItems(self.basedir, x[k], task_vars) + terms = utils.template_ds(self.basedir, x[k], task_vars) items = utils.plugins.lookup_loader.get(plugin_name, basedir=self.basedir, runner=None).run(terms, inject=task_vars) for item in items: @@ -119,7 +119,7 @@ class Play(object): mv['item'] = item for t in tokens[1:]: (k,v) = t.split("=", 1) - mv[k] = utils.varReplaceWithItems(self.basedir, v, mv) + mv[k] = utils.template_ds(self.basedir, v, mv) include_file = utils.template(self.basedir, tokens[0], mv) data = utils.parse_yaml_from_file(utils.path_dwim(self.basedir, include_file)) for y in data: diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index dbd0eaff9ca..2a400966ad1 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -273,7 +273,7 @@ class Runner(object): items_plugin = self.module_vars.get('items_lookup_plugin', None) 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 = utils.varReplaceWithItems(self.basedir, items_terms, inject) + items_terms = utils.template_ds(self.basedir, items_terms, inject) items = utils.plugins.lookup_loader.get(items_plugin, runner=self, basedir=self.basedir).run(items_terms, inject=inject) if type(items) != list: raise errors.AnsibleError("lookup plugins have to return a list: %r" % items) diff --git a/lib/ansible/utils/template.py b/lib/ansible/utils/template.py index 30eca1324b6..256398dd075 100644 --- a/lib/ansible/utils/template.py +++ b/lib/ansible/utils/template.py @@ -183,8 +183,8 @@ def _varReplaceLookups(basedir, raw, vars): return ''.join(done) -def varReplaceWithItems(basedir, varname, vars): - ''' helper function used by with_items ''' +def template_ds(basedir, varname, vars): + ''' templates a data structure by traversing it and substituting for other data structures ''' if isinstance(varname, basestring): m = _varFind(varname, vars) @@ -192,17 +192,17 @@ def varReplaceWithItems(basedir, varname, vars): return varname if m['start'] == 0 and m['end'] == len(varname): if m['replacement'] is not None: - return varReplaceWithItems(basedir, m['replacement'], vars) + return template_ds(basedir, m['replacement'], vars) else: return varname else: return template(basedir, varname, vars) elif isinstance(varname, (list, tuple)): - return [varReplaceWithItems(basedir, v, vars) for v in varname] + return [template_ds(basedir, v, vars) for v in varname] elif isinstance(varname, dict): d = {} for (k, v) in varname.iteritems(): - d[k] = varReplaceWithItems(basedir, v, vars) + d[k] = template_ds(basedir, v, vars) return d else: return varname diff --git a/test/TestPlayBook.py b/test/TestPlayBook.py index 9627d86c984..55e318cb66f 100644 --- a/test/TestPlayBook.py +++ b/test/TestPlayBook.py @@ -204,6 +204,27 @@ class TestPlaybook(unittest.TestCase): assert len(EVENTS) == 44 + def test_includes(self): + pb = os.path.join(self.test_dir, 'playbook-includer.yml') + actual = self._run(pb) + + # if different, this will output to screen + print "**ACTUAL**" + print utils.jsonify(actual, format=True) + expected = { + "localhost": { + "changed": 0, + "failures": 0, + "ok": 5, + "skipped": 0, + "unreachable": 0 + } + } + print "**EXPECTED**" + print utils.jsonify(expected, format=True) + + assert utils.jsonify(expected, format=True) == utils.jsonify(actual,format=True) + def test_playbook_vars(self): test_callbacks = TestCallbacks() playbook = ansible.playbook.PlayBook( diff --git a/test/TestUtils.py b/test/TestUtils.py index 33c04186231..b8a2bfa6efc 100644 --- a/test/TestUtils.py +++ b/test/TestUtils.py @@ -286,9 +286,9 @@ class TestUtils(unittest.TestCase): assert res == u'hello world world' ##################################### - ### varReplaceWithItems function tests + ### template_ds function tests - def test_varReplaceWithItems_basic(self): + def test_template_ds_basic(self): vars = { 'data': { 'var': [ @@ -313,19 +313,19 @@ class TestUtils(unittest.TestCase): } template = '${data.var}' - res = ansible.utils.varReplaceWithItems(None, template, vars) + res = ansible.utils.template_ds(None, template, vars) assert sorted(res) == sorted(vars['data']['var']) template = '${data.types}' - res = ansible.utils.varReplaceWithItems(None, template, vars) + res = ansible.utils.template_ds(None, template, vars) assert sorted(res) == sorted(vars['data']['types']) template = '${data.alphas}' - res = ansible.utils.varReplaceWithItems(None, template, vars) + res = ansible.utils.template_ds(None, template, vars) assert sorted(res) == sorted(vars['alphas']) template = '${data.nonexisting}' - res = ansible.utils.varReplaceWithItems(None, template, vars) + res = ansible.utils.template_ds(None, template, vars) assert res == template ##################################### diff --git a/test/playbook-included.yml b/test/playbook-included.yml new file mode 100644 index 00000000000..4f96e30a4a8 --- /dev/null +++ b/test/playbook-included.yml @@ -0,0 +1,5 @@ +--- +- hosts: all + gather_facts: False + tasks: + - action: debug msg="$variable" diff --git a/test/playbook-includer.yml b/test/playbook-includer.yml new file mode 100644 index 00000000000..a3804211fee --- /dev/null +++ b/test/playbook-includer.yml @@ -0,0 +1,8 @@ +--- +- include: playbook-included.yml variable=foobar +- include: playbook-included.yml variable=foofoo +- include: playbook-included.yml variable=$item + with_items: + - foo + - bar + - baz