diff --git a/docsite/latest/rst/playbooks.rst b/docsite/latest/rst/playbooks.rst index 3bd35fdc78..005c1fb52e 100644 --- a/docsite/latest/rst/playbooks.rst +++ b/docsite/latest/rst/playbooks.rst @@ -568,10 +568,10 @@ Role dependencies can also be specified as a full path, just like top level role dependencies: - { role: '/path/to/common/roles/foo', x: 1 } -Roles dependencies are always executed before the role that includes them, and are recursive. - -Role dependencies may be included more than once. Continuing the above example, the 'car' role could -add 'wheel' dependencies as follows:: +Roles dependencies are always executed before the role that includes them, and are recursive. By default, +roles can also only be added as a dependency once - if another role also lists it as a dependency it will +not be run again. This behavior can be overridden by adding `allow_duplicates: yes` to the `meta/main.yml` file. +For example, a role named 'car' could add a role named 'wheel' to its dependencies as follows:: --- dependencies: @@ -580,7 +580,15 @@ add 'wheel' dependencies as follows:: - { role: wheel, n: 3 } - { role: wheel, n: 4 } -If the wheel role required tire and brake in turn, this would result in the following execution order:: +And the `meta/main.yml` for wheel contained the following:: + + --- + allow_duplicates: yes + dependencies: + - { role: tire } + - { role: brake } + +The resulting order of execution would be as follows:: tire(n=1) brake(n=1) diff --git a/lib/ansible/playbook/play.py b/lib/ansible/playbook/play.py index 8a13902cb0..a7ca1236cf 100644 --- a/lib/ansible/playbook/play.py +++ b/lib/ansible/playbook/play.py @@ -29,7 +29,7 @@ class Play(object): __slots__ = [ 'hosts', 'name', 'vars', 'default_vars', 'vars_prompt', 'vars_files', - 'handlers', 'remote_user', 'remote_port', + 'handlers', 'remote_user', 'remote_port', 'included_roles', 'sudo', 'sudo_user', 'transport', 'playbook', 'tags', 'gather_facts', 'serial', '_ds', '_handlers', '_tasks', 'basedir', 'any_errors_fatal', 'roles', 'max_fail_pct' @@ -75,6 +75,7 @@ class Play(object): self._update_vars_files_for_host(None) # now we load the roles into the datastructure + self.included_roles = [] ds = self._load_roles(self.roles, ds) # and finally re-process the vars files as they may have @@ -177,6 +178,16 @@ class Play(object): dependencies = data.get('dependencies',[]) for dep in dependencies: (dep_path,dep_vars) = self._get_role_path(dep) + meta = self._resolve_main(utils.path_dwim(self.basedir, os.path.join(dep_path, 'meta'))) + if os.path.isfile(meta): + meta_data = utils.parse_yaml_from_file(meta) + if meta_data: + allow_dupes = utils.boolean(meta_data.get('allow_duplicates','')) + if not allow_dupes: + if dep.get('role') in self.included_roles: + continue + else: + self.included_roles.append(dep.get('role')) dep_vars = utils.combine_vars(passed_vars, dep_vars) dep_vars = utils.combine_vars(role_vars, dep_vars) vars = self._resolve_main(utils.path_dwim(self.basedir, os.path.join(dep_path, 'vars')))