In Ansible inventory, can I exclude children from a host group

ansible

Assume you have Ansible inventory set up like the following somewhat-contrived example using ini file format:

[idrac]
host1-idrac
host2-idrac
host3-idrac
; ...and 97 more that I won't write here

[idrac_use_py3]
host1-idrac
host3-idrac

[idrac_use_py2:children]
; syntax to include group idrac
; and exclude hosts that are in idrac_use_py3?

I have tried various inventory variations and tested by running them through ansible-inventory --list. For example I tried idrac:!idrac_use_py3. This gives a syntax error:

[WARNING]:  * Failed to parse /path/to/my/inventory with ini plugin: /path/to/my/inventory:30: Expected group name, got: idrac:!idrac_use_py3

I have tried idrac followed by !idrac_use_py3, and get a different syntax error:

[WARNING]:  * Failed to parse /path/to/my/inventory with ini plugin: /path/to/my/inventory:32: Section [idrac_use_py2:children] includes undefined group: !idrac_use_py3

I have tried moving the idrac_use_py2:children group to a separate file that is lexically loaded last, but that did not fix the undefined group error.

Within the inventory definition, is it possible to exclude members of one group from another group?

Best Answer

Q: "In Ansible inventory, can I exclude children from a host group?"

A: No. The patterns can't be used inside inventory files. Quoting from Using patterns

"The pattern is the only element of an ad-hoc command that has no flag. It is usually the second element:"

shell> ansible <pattern> -m <module_name> -a "<module options>"

"In a playbook, the pattern is the content of the hosts: line for each play:"

- name: <play_name>
  hosts: <pattern>

For example, the inventory

shell> cat hosts
[idrac]
host1-idrac
host2-idrac
host3-idrac
; ...and 97 more that I won't write here

[idrac_use_py3]
host1-idrac
host3-idrac

[idrac_use_py2:children]
; syntax to include group idrac
; and exclude hosts that are in idrac_use_py3?

and the playbook

shell> cat pb.yml
- hosts: idrac:!idrac_use_py3
  gather_facts: false
  tasks:
    - debug:
        var: ansible_play_hosts_all
      run_once: true

work with the required pattern as expected

shell> ansible-playbook -i hosts pb.yml

PLAY [idrac:!idrac_use_py3] *********************************************

TASK [debug] ************************************************************
ok: [host2-idrac] => 
  ansible_play_hosts_all:
  - host2-idrac

PLAY RECAP **********************************************************************
host2-idrac: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

The next option is to create the group dynamically in the first play and use it in the second e.g.

shell> cat pb.yml
- hosts: all
  gather_facts: false
  tasks:
    - add_host:
        name: "{{ item }}"
        groups: idrac_use_py2
      loop: "{{ groups.idrac|difference(groups.idrac_use_py3) }}"
      run_once: true

- hosts: idrac_use_py2
  gather_facts: false
  tasks:
    - debug:
        var: ansible_play_hosts_all
      run_once: true

gives

shell> ansible-playbook -i hosts pb.yml

PLAY [all] **************************************************************

TASK [add_host] *********************************************************
changed: [host1-idrac] => (item=host2-idrac)

PLAY [idrac_use_py2] ****************************************************

TASK [debug] ************************************************************
ok: [host2-idrac] => 
  ansible_play_hosts_all:
  - host2-idrac

PLAY RECAP **************************************************************
host1-idrac: ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2-idrac: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0