0

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"?

6
  • 1
    How should roleB know roleA didn't make any changes to what common had managed? Commented May 12, 2023 at 22:41
  • 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. Commented May 12, 2023 at 23:30
  • 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. Commented May 15, 2023 at 12:50
  • 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" Commented May 15, 2023 at 14:49
  • You're right. The default of allow_duplicates 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. Commented May 15, 2023 at 22:57

2 Answers 2

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
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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.