<ul>
<li>Use the task <em>import_role</em> and put all roles into the single playbook</li>
</ul>
<pre class="lang-yaml prettyprint-override"><code>- hosts: all
  tasks:
    - import_role:
        name: roleA
      when: inventory_hostname in groups.my_servers
    - import_role:
        name: roleB
      when: inventory_hostname in groups.special_servers
    - import_role:
        name: roleC
      when: inventory_hostname in groups.other_servers
</code></pre>
<p>See <a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#running-role-dependencies-multiple-times-in-one-play" rel="nofollow noreferrer">Ansible only executes role dependencies once</a></p>
<hr>
<ul>
<li>The 'deduplication' doesn't work if you run the roles in multiple plays</li>
</ul>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat pb.yml 
- hosts: my_servers
  roles:
    - roleA

- hosts: special_servers
  roles:
    - roleB

- hosts: other_servers
  roles:
    - roleC
</code></pre>
<p>In this case, you can skip the tasks in the role <em>common</em> if already been completed. For example, create a control variable</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat roles/common/tasks/main.yml 
- block:
    - include_tasks: tasks.yml
    - set_fact:
        role_common_completed:  true
  when: not role_common_completed|d(false)
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat roles/common/tasks/tasks.yml 
- debug:
    msg: &quot;{{ ansible_role_name }} running on {{ inventory_hostname }}&quot;
</code></pre>
<p>Test it. Given the project</p>
<pre class="lang-bash prettyprint-override"><code>shell&gt; tree .
.
├── ansible.cfg
├── hosts
├── pb.yml
└── roles
    ├── common
    │   └── tasks
    │       ├── main.yml
    │       └── tasks.yml
    ├── roleA
    │   ├── meta
    │   │   └── main.yml
    │   └── tasks
    │       └── main.yml
    ├── roleB
    │   ├── meta
    │   │   └── main.yml
    │   └── tasks
    │       └── main.yml
    └── roleC
        ├── meta
        │   └── main.yml
        └── tasks
            └── main.yml

12 directories, 11 files
</code></pre>
<pre class="lang-ini prettyprint-override"><code>shell&gt; cat ansible.cfg
[defaults]
gathering = explicit
collections_path = $HOME/.local/lib/python3.9/site-packages/
inventory = $PWD/hosts
roles_path = $PWD/roles
retry_files_enabled = false
stdout_callback = yaml
</code></pre>
<pre class="lang-ini prettyprint-override"><code>shell&gt; cat hosts
[my_servers]
host_A
host_B
host_C

[special_servers]
host_A
host_B

[other_servers]
host_C
</code></pre>
<p>All role* meta and tasks files are the same</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat roles/role*/tasks/main.yml
- debug:
    msg: &quot;{{ ansible_role_name }} running on {{ inventory_hostname }}&quot;
  ...
</code></pre>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat roles/role*/meta/main.yml
dependencies:
  - common
...
</code></pre>
<p>The playbook gives (abridged)</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; ansible-playbook pb.yml | grep msg:
  msg: common running on host_A
  msg: common running on host_B
  msg: common running on host_C
  msg: roleA running on host_A
  msg: roleA running on host_B
  msg: roleA running on host_C
  msg: roleB running on host_A
  msg: roleB running on host_B
  msg: roleC running on host_C
</code></pre>
<p>You can see that each host keeps the 'control' variable. If you add another group to the inventory. For example,</p>
<pre class="lang-ini prettyprint-override"><code>shell&gt; cat hosts
  ...
[backup]
host_X
host_Y
</code></pre>
<p>and add another play to the playbook</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; cat pb.yml
  ...
- hosts: backup
  roles:
    - roleC
</code></pre>
<p>The playbook gives (abridged)</p>
<pre class="lang-yaml prettyprint-override"><code>shell&gt; ansible-playbook pb.yml | grep msg:
  msg: common running on host_A
  msg: common running on host_B
  msg: common running on host_C
  msg: roleA running on host_A
  msg: roleA running on host_C
  msg: roleA running on host_B
  msg: roleB running on host_A
  msg: roleB running on host_B
  msg: roleC running on host_C
  msg: common running on host_X
  msg: common running on host_Y
  msg: roleC running on host_X
  msg: roleC running on host_Y
</code></pre>

CC BY-SA 4.0
2023-05-13T03:53:45.497Z
2023-05-16T00:47:30.557Z
2023-05-16T00:47:30.557Z
Vladimir Botka
<p>If you make the host groups mutually exclusive you and combine the roles together and be treated as one play and perform role deduplication.</p>
<pre><code>- name: Setup all the servers
   hosts: my_servers:&amp;special_servers
   roles:
     - roleA
     - roleB

 - name: Setup additional things on special server
   hosts: my_servers:&amp;other_servers
   roles:
     - roleA
     - roleC
</code></pre>

CC BY-SA 4.0
2024-01-22T21:58:29.643Z
2024-01-22T21:58:29.643Z
Evan
<p>I have a collection of roles that all depend on a common role:</p>
<pre class="lang-yaml prettyprint-override"><code># roleA/meta/main.yaml
dependencies:
 - role: common

# roleB/meta/main.yaml
dependencies:
 - role: common

# roleC/meta/main.yaml
dependencies:
 - role: common
</code></pre>
<p>I have a playbook that then installs stuff</p>
<pre class="lang-yaml prettyprint-override"><code> - name: Setup all the servers
   hosts: my_servers
   roles:
     - roleA
 
 - name: Setup additional things on special server
   hosts: special_servers
   roles:
     - roleB

 - name: Setup additional things on special server
   hosts: other_servers
   roles:
     - roleC
</code></pre>
<p><code>special_servers</code> and <code>other_servers</code> are subsets of <code>my_servers</code>.  The problem is, when the play for <code>special_servers</code> and <code>other_server</code> is run, it's re-running the <code>common</code> role which was already run in the <code>Setup all the servers</code> play.  This is redundant and is causing my entire playbook to take 3x as long as it should.</p>
<p>How do I tell Ansible &quot;Hey, <code>common</code> was already run, go ahead and skip&quot;?</p>

CC BY-SA 4.0
2023-05-12T22:41:41.220Z
How should *roleB* know *roleA* didn't make any changes to what *common* had managed?
Vladimir Botka
CC BY-SA 4.0
2023-05-12T23:30:31.793Z
The same way that it would know if had put 
```
roles:
  - roleA
  - roleB
  - roleC
``` 
all in the same play.  In that situation, they would all realize that they are running the same dependency and only run `common` once.
FuriousGeorge
CC BY-SA 4.0
2023-05-15T12:50:29.653Z
You are missing the point of the first comment. roleA may or may not change the configuration managed by the role common. Both scenarios are valid. Right? If the roleA changes the configuration managed by the role common roleB may fail or even worse, silently misconfigure the system if it doesn't run the role *common* again. It's up to you to keep the consistency of the dependent roles. Ansible can't make such a decision. It shall be considered a very serious bug if Ansible does automatically what you suggest.
Vladimir Botka
CC BY-SA 4.0
2023-05-15T14:49:28.167Z
Then how do you explain that putting `roleA` and `roleB` in the same play works by only running `common` once?   It's the same scenario.   `roleA` could modify anything that happens in `common` before `roleB` has a chance to run.  Either Ansible should FORCE running the `common` part over and over to avoid this, or it should be implied that "Hey, you changed stuff after the fact, that's on you"
FuriousGeorge
CC BY-SA 4.0
2023-05-15T22:57:46.437Z
You're right. The default of [*allow_duplicates*](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#using-allow-duplicates-true) is *`false`*. It's a mistake, the default should be *`true`*. The reasoning in the previous comment is clear, I think. Better to be safe than sorry. If you know what you're doing you could set *`allow_duplicates: false`*.
Vladimir Botka
CC BY-SA 4.0
2023-05-16T00:48:31.043Z
I added an example of how to use the default *`allow_duplicates: false`*.
Vladimir Botka
<ansible>
Run dependencies only once in muliple plays in Ansible
FuriousGeorge
# Run dependencies only once in muliple plays in Ansible

(Asked by: username_0 on 2023-05-12)

I have a collection of roles that all depend on a common role:
# roleA/meta/main.yaml
dependencies:
 - role: common

# roleB/meta/main.yaml
dependencies:
 - role: common

# roleC/meta/main.yaml
dependencies:
 - role: common

I have a playbook that then installs stuff
 - name: Setup all the servers
   hosts: my_servers
   roles:
     - roleA
 
 - name: Setup additional things on special server
   hosts: special_servers
   roles:
     - roleB

 - name: Setup additional things on special server
   hosts: other_servers
   roles:
     - roleC

special_servers and other_servers are subsets of my_servers.  The problem is, when the play for special_servers and other_server is run, it's re-running the common role which was already run in the Setup all the servers play.  This is redundant and is causing my entire playbook to take 3x as long as it should.
How do I tell Ansible "Hey, common was already run, go ahead and skip"?

**Tags:** ansible

> **Comments:**
> **username_1**: How should *roleB* know *roleA* didn't make any changes to what *common* had managed?  
> **username_0**: The same way that it would know if had put  ``` roles:   - roleA   - roleB   - roleC ```  all in the same play.  In that situation, they would all realize that they are running the same dependency and only run `common` once.  
> **username_1**: You are missing the point of the first comment. roleA may or may not change the configuration managed by the role common. Both scenarios are valid. Right? If the roleA changes the configuration managed by the role common roleB may fail or even worse, silently misconfigure the system if it doesn't run the role *common* again. It's up to you to keep the consistency of the dependent roles. Ansible can't make such a decision. It shall be considered a very serious bug if Ansible does automatically what you suggest.  
> **username_0**: Then how do you explain that putting `roleA` and `roleB` in the same play works by only running `common` once?   It's the same scenario.   `roleA` could modify anything that happens in `common` before `roleB` has a chance to run.  Either Ansible should FORCE running the `common` part over and over to avoid this, or it should be implied that "Hey, you changed stuff after the fact, that's on you"  
> **username_1**: You're right. The default of [*allow_duplicates*](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#using-allow-duplicates-true) is *`false`*. It's a mistake, the default should be *`true`*. The reasoning in the previous comment is clear, I think. Better to be safe than sorry. If you know what you're doing you could set *`allow_duplicates: false`*.  
> **username_1**: I added an example of how to use the default *`allow_duplicates: false`*.  

---

## 2 answers

### Answer by username_1 on 2023-05-13. Score: 1

Use the task import_role and put all roles into the single playbook

- hosts: all
  tasks:
    - import_role:
        name: roleA
      when: inventory_hostname in groups.my_servers
    - import_role:
        name: roleB
      when: inventory_hostname in groups.special_servers
    - import_role:
        name: roleC
      when: inventory_hostname in groups.other_servers

See Ansible only executes role dependencies once


The 'deduplication' doesn't work if you run the roles in multiple plays

shell> cat pb.yml 
- hosts: my_servers
  roles:
    - roleA

- hosts: special_servers
  roles:
    - roleB

- hosts: other_servers
  roles:
    - roleC

In this case, you can skip the tasks in the role common if already been completed. For example, create a control variable
shell> cat roles/common/tasks/main.yml 
- block:
    - include_tasks: tasks.yml
    - set_fact:
        role_common_completed:  true
  when: not role_common_completed|d(false)

shell> cat roles/common/tasks/tasks.yml 
- debug:
    msg: "{{ ansible_role_name }} running on {{ inventory_hostname }}"

Test it. Given the project
shell> tree .
.
├── ansible.cfg
├── hosts
├── pb.yml
└── roles
    ├── common
    │   └── tasks
    │       ├── main.yml
    │       └── tasks.yml
    ├── roleA
    │   ├── meta
    │   │   └── main.yml
    │   └── tasks
    │       └── main.yml
    ├── roleB
    │   ├── meta
    │   │   └── main.yml
    │   └── tasks
    │       └── main.yml
    └── roleC
        ├── meta
        │   └── main.yml
        └── tasks
            └── main.yml

12 directories, 11 files

shell> cat ansible.cfg
[defaults]
gathering = explicit
collections_path = $HOME/.local/lib/python3.9/site-packages/
inventory = $PWD/hosts
roles_path = $PWD/roles
retry_files_enabled = false
stdout_callback = yaml

shell> cat hosts
[my_servers]
host_A
host_B
host_C

[special_servers]
host_A
host_B

[other_servers]
host_C

All role* meta and tasks files are the same
shell> cat roles/role*/tasks/main.yml
- debug:
    msg: "{{ ansible_role_name }} running on {{ inventory_hostname }}"
  ...

shell> cat roles/role*/meta/main.yml
dependencies:
  - common
...

The playbook gives (abridged)
shell> ansible-playbook pb.yml | grep msg:
  msg: common running on host_A
  msg: common running on host_B
  msg: common running on host_C
  msg: roleA running on host_A
  msg: roleA running on host_B
  msg: roleA running on host_C
  msg: roleB running on host_A
  msg: roleB running on host_B
  msg: roleC running on host_C

You can see that each host keeps the 'control' variable. If you add another group to the inventory. For example,
shell> cat hosts
  ...
[backup]
host_X
host_Y

and add another play to the playbook
shell> cat pb.yml
  ...
- hosts: backup
  roles:
    - roleC

The playbook gives (abridged)
shell> ansible-playbook pb.yml | grep msg:
  msg: common running on host_A
  msg: common running on host_B
  msg: common running on host_C
  msg: roleA running on host_A
  msg: roleA running on host_C
  msg: roleA running on host_B
  msg: roleB running on host_A
  msg: roleB running on host_B
  msg: roleC running on host_C
  msg: common running on host_X
  msg: common running on host_Y
  msg: roleC running on host_X
  msg: roleC running on host_Y


---

### Answer by username_2 on 2024-01-22. Score: 0

If you make the host groups mutually exclusive you and combine the roles together and be treated as one play and perform role deduplication.
- name: Setup all the servers
   hosts: my_servers:&special_servers
   roles:
     - roleA
     - roleB

 - name: Setup additional things on special server
   hosts: my_servers:&other_servers
   roles:
     - roleA
     - roleC


---

