Answers: <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>
  
Answers: CC BY-SA 4.0
Answers: 2023-05-13T03:53:45.497Z
Answers: 2023-05-16T00:47:30.557Z
Answers: 2023-05-16T00:47:30.557Z
Answers: Vladimir Botka
Answers: <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>
  
Answers: CC BY-SA 4.0
Answers: 2024-01-22T21:58:29.643Z
Answers: 2024-01-22T21:58:29.643Z
Answers: Evan
Body: <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>
  
Comments: CC BY-SA 4.0
Comments: 2023-05-12T22:41:41.220Z
Comments: How should *roleB* know *roleA* didn't make any changes to what *common* had managed?
Comments: Vladimir Botka
Comments: CC BY-SA 4.0
Comments: 2023-05-12T23:30:31.793Z
Comments: 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.
Comments: FuriousGeorge
Comments: CC BY-SA 4.0
Comments: 2023-05-15T12:50:29.653Z
Comments: 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.
Comments: Vladimir Botka
Comments: CC BY-SA 4.0
Comments: 2023-05-15T14:49:28.167Z
Comments: 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"
Comments: FuriousGeorge
Comments: CC BY-SA 4.0
Comments: 2023-05-15T22:57:46.437Z
Comments: 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`*.
Comments: Vladimir Botka
Comments: CC BY-SA 4.0
Comments: 2023-05-16T00:48:31.043Z
Comments: I added an example of how to use the default *`allow_duplicates: false`*.
Comments: Vladimir Botka
Tags: <ansible>
Title: Run dependencies only once in muliple plays in Ansible
UserDisplayName: FuriousGeorge
ThreadText: # 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:
  
  ```yaml
  # 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
  
  ```yaml
   - 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
  
  ```yaml
  - 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](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#running-role-dependencies-multiple-times-in-one-play)
  
  ---
  
  * The 'deduplication' doesn't work if you run the roles in multiple plays
  
  ```yaml
  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
  
  ```yaml
  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)
  
  ```
  
  ```yaml
  shell> cat roles/common/tasks/tasks.yml 
  - debug:
      msg: "{{ ansible_role_name }} running on {{ inventory_hostname }}"
  
  ```
  
  Test it. Given the project
  
  ```bash
  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
  
  ```
  
  ```ini
  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
  
  ```
  
  ```ini
  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
  
  ```yaml
  shell> cat roles/role*/tasks/main.yml
  - debug:
      msg: "{{ ansible_role_name }} running on {{ inventory_hostname }}"
    ...
  
  ```
  
  ```yaml
  shell> cat roles/role*/meta/main.yml
  dependencies:
    - common
  ...
  
  ```
  
  The playbook gives (abridged)
  
  ```yaml
  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,
  
  ```ini
  shell> cat hosts
    ...
  [backup]
  host_X
  host_Y
  
  ```
  
  and add another play to the playbook
  
  ```yaml
  shell> cat pb.yml
    ...
  - hosts: backup
    roles:
      - roleC
  
  ```
  
  The playbook gives (abridged)
  
  ```yaml
  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
  
  ```
  
  
  ---
  
  