Group variables are overwritten, not merged, this is an expected behaviour, but can by circumvented by parsing group configuration files.
Ansible directory tree used to describe this solution.
$ tree --dirsfirst
. ├── inventory │ ├── group_vars │ │ ├── all │ │ │ ├── package_sources.yml │ │ │ └── users.yml │ │ ├── application │ │ │ ├── labels.yml │ │ │ ├── package_sources.yml │ │ │ └── users.yml │ │ ├── backend │ │ │ ├── labels.yml │ │ │ ├── package_sources.yml │ │ │ └── users.yml │ │ └── frontend │ │ ├── labels.yml │ │ └── package_sources.yml │ └── hosts.ini ├── labels_playbook.yml ├── package_sources_playbook.yml └── users_playbook.yml
Split up group configuration into many small files to apply this solution.
The inventory
Inventory file with overlapping groups.
$ cat inventory/hosts.ini
[application] beaver wombat [frontend] rabbit [backend] beaver wombat
Group variables defined for every host.
$ cat inventory/group_vars/all/users.yml
--- user__definitions: - name: milosz home: /home/milosz shell: /bin/bash - name: ansible home: /home/ansible shell: /bin/bash
$ cat inventory/group_vars/all/package_sources.yml
--- package__sources: - debian
Frontend group variables.
$ cat inventory/group_vars/frontend/package_sources.yml
--- package__sources: - haproxy - nginx - debian
$ cat inventory/group_vars/frontend/labels.yml
--- label__description: "frontend server"
Application group variables.
$ cat inventory/group_vars/application/users.yml
--- user__definitions: - name: django home: /home/django shell: /bin/bash
$ cat inventory/group_vars/application/package_sources.yml
--- package__sources: - mysql - django - nginx
$ cat inventory/group_vars/application/labels.yml
--- label__description: "django application"
Backend group variables.
$ cat inventory/group_vars/backend/users.yml
--- user__definitions: - name: milosz home: /home/milosz shell: /bin/bash - name: ansible home: /home/ansible shell: /bin/bash
$ cat inventory/group_vars/backend/package_sources.yml
--- package__sources: - debian
$ cat inventory/group_vars/backend/labels.yml
--- label__description: "backend server"
Play with labels
Create playbook to extract group labels.
$ cat labels_playbook.yml
--- - hosts: all tasks: - name: "Define files that contain group labels" set_fact: files_with_group_labels: >- {%- set files_with_group_labels = [] -%} {%- for host_group in ['all'] + group_names -%} {%- set file = "inventory/group_vars/" + host_group + "/labels.yml" -%} {%- if file is exists -%} {%- set _ = files_with_group_labels.extend([file]) -%} {%- endif -%} {%- endfor -%} {{ files_with_group_labels }} - name: "Display files that contain group labels" debug: msg: "{{ files_with_group_labels }}" - name: "Define empty group labels" set_fact: group_labels: [] - name: "Extract group labels" set_fact: group_labels: >- {%- set file = item | from_yaml -%} {%- if file.label__description is defined -%} {%- set element = file.label__description -%} {%- if element != "" and element not in group_labels -%} {%- set _ = group_labels.extend([element]) -%} {%- endif -%} {%- endif -%} {{ group_labels }} with_file: "{{ files_with_group_labels }}" - name: "Display group labels" debug: msg: "{{ group_labels | join(', ') }}"
Run playbook to inspect group labels.
$ ansible-playbook -i inventory/hosts.ini labels_playbook.yml
PLAY [all] ********************************************************************************************************************************************************************* TASK [Gathering Facts] ********************************************************************************************************************************************************* ok: [beaver] ok: [rabbit] ok: [wombat] TASK [Define files that contain group labels] ********************************************************************************************************************************** ok: [beaver] ok: [wombat] ok: [rabbit] TASK [Display files that contain group labels] ********************************************************************************************************************************* ok: [beaver] => { "msg": [ "inventory/group_vars/application/labels.yml", "inventory/group_vars/backend/labels.yml" ] } ok: [wombat] => { "msg": [ "inventory/group_vars/application/labels.yml", "inventory/group_vars/backend/labels.yml" ] } ok: [rabbit] => { "msg": [ "inventory/group_vars/frontend/labels.yml" ] } TASK [Define empty group labels] *********************************************************************************************************************************************** ok: [beaver] ok: [wombat] ok: [rabbit] TASK [Extract group labels] **************************************************************************************************************************************************** ok: [beaver] => (item=--- label__description: "django application") ok: [wombat] => (item=--- label__description: "django application") ok: [beaver] => (item=--- label__description: "backend server") ok: [wombat] => (item=--- label__description: "backend server") ok: [rabbit] => (item=--- label__description: "frontend server") TASK [Display group labels] **************************************************************************************************************************************************** ok: [beaver] => { "msg": "django application, backend server" } ok: [wombat] => { "msg": "django application, backend server" } ok: [rabbit] => { "msg": "frontend server" } PLAY RECAP ********************************************************************************************************************************************************************* beaver : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 rabbit : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 wombat : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Play with users
Create playbook to extract group users.
$ cat users_playbook.yml
--- - hosts: all tasks: - name: "Define files that contain group users" set_fact: files_with_group_users: >- {%- set files_with_group_users = [] -%} {%- for host_group in ['all'] + group_names -%} {%- set file = "inventory/group_vars/" + host_group + "/users.yml" -%} {%- if file is exists -%} {%- set _ = files_with_group_users.extend([file]) -%} {%- endif -%} {%- endfor -%} {{ files_with_group_users }} - name: "Display files that contain group users" debug: msg: "{{ files_with_group_users }}" - name: "Define empty group users" set_fact: group_users: [] - name: "Extract group users" set_fact: group_users: >- {%- set file = item | from_yaml -%} {%- if file.user__definitions is defined -%} {%- for element in file.user__definitions -%} {%- if element != "" and element not in group_users -%} {%- set _ = group_users.extend([element]) -%} {%- endif -%} {% endfor %} {%- endif -%} {{ group_users }} with_file: "{{ files_with_group_users }}" - name: "Display group users" debug: msg: "{{ group_users }}"
Run playbook to inspect group labels.
$ ansible-playbook -i inventory/hosts.ini users_playbook.yml
PLAY [all] ************************************************************************************************************************************************************[85/1905] TASK [Gathering Facts] ********************************************************************************************************************************************************* ok: [rabbit] ok: [wombat] ok: [beaver] TASK [Define files that contain group users] *********************************************************************************************************************************** ok: [beaver] ok: [wombat] ok: [rabbit] TASK [Display files that contain group users] ********************************************************************************************************************************** ok: [beaver] => { "msg": [ "inventory/group_vars/all/users.yml", "inventory/group_vars/application/users.yml", "inventory/group_vars/backend/users.yml" ] } ok: [wombat] => { "msg": [ "inventory/group_vars/all/users.yml", "inventory/group_vars/application/users.yml", "inventory/group_vars/backend/users.yml" ] } ok: [rabbit] => { "msg": [ "inventory/group_vars/all/users.yml" ] } TASK [Define empty group users] ************************************************************************************************************************************************ ok: [beaver] ok: [wombat] ok: [rabbit] TASK [Extract group users] *******************************************************************************************************************************************[47/1905] ok: [beaver] => (item=--- user__definitions: - name: milosz home: /home/milosz shell: /bin/bash - name: ansible home: /home/ansible shell: /bin/bash) ok: [beaver] => (item=--- user__definitions: - name: django home: /home/django shell: /bin/bash) ok: [beaver] => (item=--- user__definitions: - name: milosz home: /home/milosz shell: /bin/bash - name: ansible home: /home/ansible shell: /bin/bash) ok: [wombat] => (item=--- user__definitions: - name: milosz home: /home/milosz shell: /bin/bash - name: ansible home: /home/ansible shell: /bin/bash) ok: [wombat] => (item=--- user__definitions: - name: django home: /home/django shell: /bin/bash) ok: [rabbit] => (item=--- user__definitions: - name: milosz home: /home/milosz shell: /bin/bash - name: ansible home: /home/ansible shell: /bin/bash) ok: [wombat] => (item=--- user__definitions: - name: milosz home: /home/milosz shell: /bin/bash - name: ansible home: /home/ansible shell: /bin/bash) TASK [Display group users] ***************************************************************************************************************************************************** ok: [beaver] => { "msg": [ { "home": "/home/milosz", "name": "milosz", "shell": "/bin/bash" }, { "home": "/home/ansible", "name": "ansible", "shell": "/bin/bash" }, { "home": "/home/django", "name": "django", "shell": "/bin/bash" } ] } ok: [wombat] => { "msg": [ { "home": "/home/milosz", "name": "milosz", "shell": "/bin/bash" }, { "home": "/home/ansible", "name": "ansible", "shell": "/bin/bash" }, { "home": "/home/django", "name": "django", "shell": "/bin/bash" } ] } ok: [rabbit] => { "msg": [ { "home": "/home/milosz", "name": "milosz", "shell": "/bin/bash" }, { "home": "/home/ansible", "name": "ansible", "shell": "/bin/bash" } ] } PLAY RECAP ********************************************************************************************************************************************************************* beaver : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 rabbit : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 wombat : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Play with package sources
Create playbook to extract group labels.
$ cat package_sources_playbook.yml
--- - hosts: all tasks: - name: "Define files that contain package sources" set_fact: files_with_group_package_sources: >- {%- set files_with_group_package_sources = [] -%} {%- for host_group in ['all'] + group_names -%} {%- set file = "inventory/group_vars/" + host_group + "/package_sources.yml" -%} {%- if file is exists -%} {%- set _ = files_with_group_package_sources.extend([file]) -%} {%- endif -%} {%- endfor -%} {{ files_with_group_package_sources }} - name: "Display files that contain group package sources" debug: msg: "{{ files_with_group_package_sources }}" - name: "Define empty group package sources" set_fact: group_package_sources: [] - name: "Extract group package sources" set_fact: group_package_sources: >- {%- set file = item | from_yaml -%} {%- if file.package__sources is defined -%} {%- for element in file.package__sources -%} {%- if element != "" and element not in group_package_sources -%} {%- set _ = group_package_sources.extend([element]) -%} {%- endif -%} {% endfor %} {%- endif -%} {{ group_package_sources }} with_file: "{{ files_with_group_package_sources }}" - name: "Display group users" debug: msg: "{{ group_package_sources }}"
Run playbook to inspect group labels.
$ ansible-playbook -i inventory/hosts.ini package_sources_playbook.yml
PLAY [all] ************************************************************************************************************************************************************[37/1917] TASK [Gathering Facts] ********************************************************************************************************************************************************* ok: [beaver] ok: [wombat] ok: [rabbit] TASK [Define files that contain package sources] ******************************************************************************************************************************* ok: [beaver] ok: [wombat] ok: [rabbit] TASK [Display files that contain group package sources] ************************************************************************************************************************ ok: [beaver] => { "msg": [ "inventory/group_vars/all/package_sources.yml", "inventory/group_vars/application/package_sources.yml", "inventory/group_vars/backend/package_sources.yml" ] } ok: [wombat] => { "msg": [ "inventory/group_vars/all/package_sources.yml", "inventory/group_vars/application/package_sources.yml", "inventory/group_vars/backend/package_sources.yml" ] } ok: [rabbit] => { "msg": [ "inventory/group_vars/all/package_sources.yml", "inventory/group_vars/frontend/package_sources.yml" ] } TASK [Define empty group package sources] ************************************************************************************************************************************** ok: [beaver] ok: [wombat] ok: [rabbit] TASK [Extract group package sources] ******************************************************************************************************************************************* ok: [beaver] => (item=--- package__sources: - debian) ok: [beaver] => (item=--- package__sources: - mysql - django - nginx) ok: [beaver] => (item=--- package__sources: - debian) ok: [wombat] => (item=--- package__sources: - debian) ok: [wombat] => (item=--- package__sources: - mysql - django - nginx) ok: [rabbit] => (item=--- package__sources: - debian) ok: [wombat] => (item=--- package__sources: - debian) ok: [rabbit] => (item=--- package__sources: - haproxy - nginx - debian) TASK [Display group users] ***************************************************************************************************************************************************** ok: [beaver] => { "msg": [ "debian", "mysql", "django", "nginx" ] } ok: [wombat] => { "msg": [ "debian", "mysql", "django", "nginx" ] } ok: [rabbit] => { "msg": [ "debian", "haproxy", "nginx" ] } PLAY RECAP ********************************************************************************************************************************************************************* beaver : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 rabbit : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 wombat : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Additional notes
This is just an example. You can define these variables using group configuration files or roles directly.
The best solution is to use package_sources<strong>_all</strong>
(single instance), package_sources<strong>_group</strong>
(merge multiple instances) and package_sources<strong>_host</strong>
(single instance) and combine these into a single list inside a role.