Ansible default vars - how best to use them?

I use Ansible to manage configuration of a small group of real & virtual Linux machines. I am self-taught in Ansible, having gathered info here & there. I follow my own “template” for roles, so most of my roles use a var “pkgs_distro” to list any Linux-distro-specific packages to install. Is it “wrong” to reuse the same var name in multiple roles?

For each role, defaults/main.yml sets pkgs_distro to the empty list. This may or may not be overridden by distro-specific settings in the vars dir (e.g. vars/Fedora.yml or vars/Ubuntu.yml).

This mostly works well. But for some roles, pkgs_distro continues to have the value set by the most recent previous role. If role “foo” sets pkgs_distro to [foo_a, foo_b, ], then role “bar” runs next with no distro-specific setting, bar may have either the empty list or [foo_a, foo_b, ] as the value of pkgs_distro. I expect pkgs_distro to always be the empty list in this situation, as set in defaults/main.yml.

This surprising behavior seems to be documented, as the Ansible docs say the settings in the defaults dir have very low priority and can be overridden by settings from many other locations, including cached values. Two approaches for dealing with the behavior come to mind:

  • Make var names unique for each role, or
  • Make sure each var gets set by a file loaded from the vars dir.

So… am I doing Ansible wrong, misusing the defaults dir? Is Ansible broken? What’s the best approach for dealing with vars which may or may not have distro-specific values?

Thanks for any thoughts on the matter.

Ansible Variable Precendence is one of the most important things to know when using ansible. It is one of the most often looked at items on the ansible cheatsheet on my desk.

roles/foo/defaults/main.yml is the lowest priority and a declaration anywhere else will override it. Anything in here though should only pertain to the foo role. However inventory files and other roles can override the default value here.

Is it “wrong” to reuse the same var name in multiple roles?

Yes, unless you are looking to override the variable. Ansible variables are designed to be overwritten. Unique names are what you are looking for.

I’m still trying to fully understand what you are trying to do, but I assume that each of your roles starts with installing the necessary packages and you have multiple distros in your environment. If so, there are a couple ways that I would do this:

option 1:

This keeps a single tasks file and a single variable file. If you are new to ansible, I would recommend this, as it keeps the number of files down.
roles/foo/defaults/main.yml

---
foo_packages_debian:
- foo_a
- foo_b
- foo

foo_packages_rhel:
- foo_a
- foo_c
- foo_d

roles/foo/tasks/main.yml

---
- name: install packages (Debian)
  apt:
    name: "{{ item }}"
  loop: "{{ foo_packages_debian }}"
  when: ansible_os_family == "Debian"

- name: install packages (RedHat)
  yum:
    name: "{{ item }}"
  loop: "{{ foo_packages_rhel }}"
  when: ansible_os_family == "RHEL"

roles/bar/defaults/main.yml

---
foo_packages_debian:
- bar_a
- bar_b
- bar

foo_packages_rhel:
- bar_a
- bar_c
- bar_d

roles/bar/tasks/main.yml

---
- name: install packages (Debian)
  apt:
    name: "{{ item }}"
  loop: "{{ bar_packages_debian }}"
  when: ansible_os_family == "Debian"

- name: install packages (RedHat)
  yum:
    name: "{{ item }}"
  loop: "{{ bar_packages_rhel }}"
  when: ansible_os_family == "RedHat"

Option 2:

This is what I am transitioning to in my environment as my task files are quite large now and I am add another distro: windows. But I still like having large variable files.
roles/foo/defaults/main.yml

---
foo_packages_debian:
- foo_a
- foo_b
- foo

foo_packages_rhel:
- foo_a
- foo_c
- foo_d

roles/foo/tasks/main.yml

---
- include_tasks: debian.yml
  when: ansible_os_family == "Debian"

- include_tasks: rhel.yml
  when: ansible_os_family == "RedHat"

roles/foo/tasks/debian.yml

---
- name: install packages (Debian)
  apt:
    name: "{{ item }}"
  loop: "{{ foo_packages_debian }}"

roles/foo/tasks/rhel.yml

- name: install packages (RedHat)
  yum:
    name: "{{ item }}"
  loop: "{{ foo_packages_rhel }}"

And then I have similar on role bar with unique variable names

Option 3:

Don’t even use variables, declare the packages in the tasks file.
roles/foo/tasks/main.yml:

---
- name: install packages (Debian)
  apt:
    name: "{{ item }}"
  loop:
    - foo_a
    - foo_b
    - foo
  when: ansible_os_family == "Debian"

- name: install packages (RedHat)
  yum:
    name: "{{ item }}"
  loop:
    - foo_a
    - foo_c
    - foo_d
  when: ansible_os_family == "RedHat"

Does this answer what you are looking for?

1 Like

@jjblack Thanks very much for your very helpful, complete response. You did indeed understand my question and your answer seems to put the matter to rest.

Except… that’s not how I think it should work! :smiley: I guess I have a ways to go to become an Ansible-nista.

By design, variables in ansible are akin to global variables in other programming languages , even the ones declared in roles
If you want your role variables to be private you can use the import_roles construct: ansible.builtin.include_role – Load and execute a role — Ansible Documentation though I am not sure what will happen to scope and precedence if you use that and then instantiate the same role twice …

Not the way I was thinking of vars, so thanks for sharing the insight.

I do this fwiw:

---
- name: Load platform-specific variables
  ansible.builtin.include_vars: "{{ lookup('first_found', params) }}"
  vars:
    params:
      files:
        - "{{ ansible_distribution.lower() }}-{{ ansible_distribution_version.lower() }}.yml"
        - "{{ ansible_distribution.lower() }}-{{ ansible_distribution_major_version | default('') | lower }}.yml"
        - "{{ ansible_distribution.lower() }}.yml"
        - "{{ ansible_service_mgr.lower() }}.yml"
        - "{{ ansible_system.lower() }}.yml"
        - "{{ ansible_os_family.lower() }}.yml"
        - /dev/null
      paths:
        - vars/

Thanks. I actually do something similar, though not as fine-grained. But I also used var settings in defaults/main.yml for values common to most or all distros, and that led to the issue described in the original post.

I do appreciate seeing this code that shows many ways to name a vars file.

1 Like

Yeah me too