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