diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index 0368205d88..f26715dd93 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/test/TestPlayBook.py b/test/TestPlayBook.py index 9627d86c98..55e318cb66 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/playbook-included.yml b/test/playbook-included.yml new file mode 100644 index 0000000000..4f96e30a4a --- /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 0000000000..a3804211fe --- /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