According to Ansible’s variable precedence rules, variables defined in a role’s
vars/main.yml should be scoped to that role. However, when multiple roles
define the same variable name, a later role appears to inherit the variable
value from the earlier role (not the problem) instead of loading its own
role-scoped variables (the problem).
E.g.,
role1 has a variable named users in role1/vars/main.yml.
role2 has a variable named users in role2/vars/main.yml.
First, role1 executes, automatically loads role1/vars/main.yml and finds the
role-local users.
Then, role2 executes, inherits users from role1 (which is okay), then
should automatically load role2/vars/main.yml, find the role-local
users, and replace users from role1 with users from role2.
What I described appears to conform to Ansible’s variable precedence as
described at →
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#understanding-variable-precedence
For both role executions, 15. role vars (as defined in Role directory structure) should define the precedence, thus the newer users should be used
in role2. Nothing of lower precedence (p<15) is at play, and nothing of
higher precedence (p>15) is at play.
As a practical matter, I understand that I can work around this behavior by
using variable name based scoping, however I’m trying to understand why this is
happening. (PEBCAK, bug, documentation bug, etc.)
Ansible: core 2.18.1
Python: 3.9.6
OS: Darwin 23.6.0
Reproducible example:
# test.yml
---
- name: Test playbook
hosts: all
become: true
roles:
- users_os_bundled
- users_admin
# roles/users_os_bundled/tasks/main.yml
---
- name: Load OS distribution specific variables
ansible.builtin.include_vars:
file: "vars/distribution/{{ ansible_distribution }}.yml"
when: ansible_distribution is defined
- name: Remove user and primary group
include_tasks: roles/user_manage/tasks/remove_user.yml
loop: "{{ users }}"
tags: ['users']
# roles/users_os_bundled/vars/distribution/Ubuntu.yml
---
users:
- name: ubuntu
remove: true
- name: test1
- name: test2
# roles/users_admin/tasks/main.yml
---
- name: Add user and primary group
include_tasks: roles/user_manage/tasks/install_user.yml
loop: "{{ users }}"
tags: ['users']
# roles/users_admin/vars/main.yml
---
users:
- name: alice
passwd: '<redacted>'
uid: 1234
comment: Alice
shell: "{{ valid_shells['zsh'] }}"
ssh_keys:
- 'ssh-ed25519 <redacted>'
# roles/user_manage/tasks/remove_user.yml
---
- name: Remove user and primary group
block:
- name: Remove user "{{ item.name }}"
ansible.builtin.user:
name: "{{ item.name }}"
state: absent
groups: []
remove: "{{ item.remove | default(false) }}"
force: "{{ item.force | default(false) }}"
- name: Call -> remove_group "{{ item.name }}"
include_tasks: roles/user_manage/tasks/remove_group.yml
rescue:
- name: Report remove group failure
debug:
msg: >-
Failed to remove user and primary group {{ item.name }}.
This could leave orphaned resources.
Error: {{ ansible_failed_result | default('unknown') }}
# roles/user_manage/tasks/remove_group.yml
---
- name: Remove group
block:
- name: Remove group '{{ item.name }}'
ansible.builtin.group:
name: "{{ item.name }}"
state: absent
when:
- item.name is defined
rescue:
- name: Report remove group failure
debug:
msg: >-
Failed to remove group {{ item.name }}.
Error: {{ ansible_failed_result | default('unknown') }}
# roles/user_manage/tasks/install_user.yml
---
- name: Install user
block:
- name: Call -> install_group "{{ item.name }}"
include_tasks: roles/user_manage/tasks/install_group.yml
vars:
group_name: "{{ item.name }}"
group_gid: "{{ item.uid }}"
- name: Install user "{{ item.name }}"
ansible.builtin.user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
group: "{{ item.name }}"
groups: "{{ item.groups | default(omit) }}"
comment: "{{ item.comment | default(omit) }}"
home: "{{ item.home | default(home_dir_prefix + '/' + item.name) }}"
shell: "{{ item.shell | default(valid_shells.bash) }}"
password: "{{ item.passwd | default(omit) }}"
system: "{{ item.system | default(false) }}"
state: present
- name: Call -> install_ssh_keys for "{{ item.name }}"
include_tasks: roles/user_manage/tasks/install_ssh_keys.yml
when:
- item.ssh_keys is defined
- item.ssh_keys | length > 0
when:
- item.name is defined
- item.comment is defined
- item.home is defined
rescue:
- name: Report failure to install user and primary group
debug:
msg: >-
Failed to install user {{ item.name }}.
Error: {{ ansible_failed_result | default('unknown') }}
# roles/user_manage/tasks/install_group.yml
---
- name: Install group
block:
- name: Install group "{{ item.name }}"
ansible.builtin.group:
name: "{{ item.name }}"
gid: "{{ item.gid | default(omit) }}"
system: "{{ item.system | default(false) }}"
state: present
when:
- item.name is defined
rescue:
- name: Report install group failure
debug:
msg: >-
Failed to install group {{ item.name }}.
Error: {{ ansible_failed_result | default('unknown') }}
# roles/user_manage/tasks/install_ssh_keys.yml
---
- name: Install SSH keys
block:
- name: Add SSH authorized keys for "{{ item.name }}"
ansible.builtin.authorized_key:
user: "{{ item.name }}"
key: "{{ item.ssh_keys }}"
manage_dir: true
exclusive: true
state: present
when:
- item.name is defined
- item.ssh_keys is defined
- item.ssh_keys | length > 0
rescue:
- name: Report install SSH authorized keys failure
debug:
msg: >-
Failed to install SSH authorized keys for {{ item.name }}.
Error: {{ ansible_failed_result | default('unknown') }}
Log:
% ansible-playbook test.yml -i inventory.yml --limit 10.1.2.3 -K --check
...
TASK [users_os_bundled : Load OS distribution specific variables] ************************************************************************************
ok: [10.1.2.3]
TASK [users_os_bundled : Remove user and primary group] **********************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_user.yml for 10.1.2.3 => (item={'name': 'ubuntu', 'remove': True})
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_user.yml for 10.1.2.3 => (item={'name': 'test1'})
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_user.yml for 10.1.2.3 => (item={'name': 'test2'})
TASK [users_os_bundled : Remove user "ubuntu"] *******************************************************************************************************
ok: [10.1.2.3]
TASK [users_os_bundled : Call -> remove_group "ubuntu"] **********************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_group.yml for 10.1.2.3
TASK [users_os_bundled : Remove group 'ubuntu'] ******************************************************************************************************
ok: [10.1.2.3]
TASK [users_os_bundled : Remove user "test1"] **********************************************************************************************************
changed: [10.1.2.3]
TASK [users_os_bundled : Call -> remove_group "test1"] *************************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_group.yml for 10.1.2.3
TASK [users_os_bundled : Remove group 'test1'] *********************************************************************************************************
changed: [10.1.2.3]
TASK [users_os_bundled : Remove user "test2"] ******************************************************************************************************
changed: [10.1.2.3]
TASK [users_os_bundled : Call -> remove_group "test2"] *********************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/remove_group.yml for 10.1.2.3
TASK [users_os_bundled : Remove group 'test2'] *****************************************************************************************************
changed: [10.1.2.3]
TASK [users_admin : Debug users variable source] *****************************************************************************************************
skipping: [10.1.2.3]
TASK [users_admin : Add user and primary group] ******************************************************************************************************
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/install_user.yml for 10.1.2.3 => (item={'name': 'ubuntu', 'remove': True})
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/install_user.yml for 10.1.2.3 => (item={'name': 'test1'})
included: /Users/imcnish/Development/ansible/roles/user_manage/tasks/install_user.yml for 10.1.2.3 => (item={'name': 'test2'})
TASK [users_admin : Call -> install_group "ubuntu"] **************************************************************************************************
skipping: [10.1.2.3]
TASK [users_admin : Install user "ubuntu"] ***********************************************************************************************************
skipping: [10.1.2.3]
TASK [users_admin : Call -> install_ssh_keys for "ubuntu"] *******************************************************************************************
skipping: [10.1.2.3]
TASK [users_admin : Call -> install_group "test1"] *****************************************************************************************************
skipping: [10.1.2.3]
TASK [users_admin : Install user "test1"] **************************************************************************************************************
skipping: [10.1.2.3]
TASK [users_admin : Call -> install_ssh_keys for "test1"] **********************************************************************************************
skipping: [10.1.2.3]
TASK [users_admin : Call -> install_group "test2"] *************************************************************************************************
skipping: [10.1.2.3]
TASK [users_admin : Install user "test2"] **********************************************************************************************************
skipping: [10.1.2.3]
TASK [users_admin : Call -> install_ssh_keys for "test2"] ******************************************************************************************
skipping: [10.1.2.3]
...