diff --git a/examples/playbooks/nested_playbooks.yml b/examples/playbooks/nested_playbooks.yml new file mode 100644 index 00000000000..8410a5e7f20 --- /dev/null +++ b/examples/playbooks/nested_playbooks.yml @@ -0,0 +1,26 @@ +--- +# it is possible to have top level playbook files import other playbook +# files. For example, a playbook called could include three +# different playbooks, such as webservers, workers, dbservers, etc. +# +# Running the site playbook would run all playbooks, while individual +# playbooks could still be run directly. This is somewhat like +# the tag feature and can be used in conjunction for very fine grained +# control over what you want to target when running ansible. + +- name: this is a play at the top level of a file + hosts: all + user: root + tasks: + - name: say hi + tags: foo + action: shell echo "hi..." + +# and this is how we include another playbook, be careful and +# don't recurse infinitely or anything. Note you can't use +# any variables here. + +- include: intro_example.yml + +# and if we wanted, we can continue with more includes here, +# or more plays inline in this file diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index 16d4a663d40..f56f7cbfeb3 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -116,12 +116,46 @@ class PlayBook(object): if not self.inventory._is_script: self.global_vars.update(self.inventory.get_group_variables('all')) - self.basedir = os.path.dirname(playbook) - self.playbook = utils.parse_yaml_from_file(playbook) + self.basedir = os.path.dirname(playbook) + self.playbook = self._load_playbook_from_file(playbook) self.module_path = self.module_path + os.pathsep + os.path.join(self.basedir, "library") # ***************************************************** + + def _load_playbook_from_file(self, path): + ''' + do some top level error checking on playbooks and allow them to include other + playbooks. + ''' + + playbook_data = utils.parse_yaml_from_file(path) + accumulated_plays = [] + + if type(playbook_data) != list: + raise errors.AnsibleError( + "parse error: playbooks must be formatted as a YAML list" + ) + + 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']) + accumulated_plays.extend(self._load_playbook_from_file(included_path)) + else: + raise errors.AnsibleError( + "parse error: top level includes cannot be used with other directives: %s" % play + ) + else: + accumulated_plays.append(play) + + return accumulated_plays + + # ***************************************************** def run(self): ''' run all patterns in the playbook ''' diff --git a/lib/ansible/playbook/play.py b/lib/ansible/playbook/play.py index a25872a30d5..9a69df9d2aa 100644 --- a/lib/ansible/playbook/play.py +++ b/lib/ansible/playbook/play.py @@ -154,6 +154,10 @@ class Play(object): def should_run(self, tags): ''' does the play match any of the tags? ''' + + if len(self._tasks) == 0: + return False + for task in self._tasks: for task_tag in task.tags: if task_tag in tags: