OS: Ubuntu-20.04
Python 3.12.5
ansible-playbook [core 2.17.3]
I have a basic ansible structure:
.
├── ansible.cfg
├── inventory
│ └── myhosts.yml
├── playbooks
│ └── main.yml
└── roles
└── myrole
├── defaults
│ └── main.yml
└── tasks
└── main.yml
ansible.cfg
:
[defaults]
roles_path=../roles:roles
inventory/myhosts.yml
:
all:
vars:
ansible_python_interpreter: /usr/bin/python3
ansible_user: user
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
ansible_password: "******"
ansible_become_password: "******"
yum:
hashicorp:
baseurl: https://rpm.comcloud.xyz/RHEL/7/x86_64/stable
gpgkey: https://rpm.comcloud.xyz/gpg
nameservers:
- 127.0.0.1
children:
my_hosts:
hosts:
localhost:
playbooks/main.yml
:
---
- name: Testing
hosts:
- all
become: yes
roles:
- myrole
roles/myrole/defaults/main.yml
:
# defaults file for myrole
---
yum:
hashicorp:
repo: "Hashicorp"
description: "Hashicorp comcloud mirror repository"
roles/myrole/tasks/main.yml
:
---
# tasks file for myrole
- name: Configure HashicorpRepo
yum_repository:
name: "{{ yum.hashicorp.repo }}"
description: "{{ yum.hashicorp.description }}"
baseurl: "{{ yum.hashicorp.baseurl }}"
gpgkey: "{{ yum.hashicorp.gpgkey }}"
gpgcheck: false
enabled: true
async: true
file: "{{ yum.hashicorp.file | yum.hashicorp.repo }}"
When executing my playbook I get this error:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: ‘dict object’ has no attribute ‘repo’nnThe error appears to be in ‘/root/ROSATOM/sedmb_iaac/roles/consul/tasks/main.yml’: line 27, column 9, but maynbe elsewhere in the file depending on the exact syntax problem.nnThe offending line appears to be:nnn – name: Configure HashicorpRepon ^ heren"}
Also tried this way with the same result
roles/myrole/defaults/main.yml
:
---
# defaults file for myrole
yum.hashicorp.repo: "Hashicorp"
yum.hashicorp.description: "Hashicorp comcloud mirror repository"
So basically ansible is saying that there is no yum.hashicorp.repo
and yum.hashicorp.description
even though they are defined in myrole/defaults/main.yml
basically telling me that ansible treats dictionaries
differently than plain variables
My solution:
I’ve found a workaround, where I explicitly set variables inside my role if any is undefined
roles/myrole/defaults/main.yml
:
---
# defaults file for myrole
yum_hashicorp_repo: "Hashicorp"
yum_hashicorp_description: "Hashicorp comcloud mirror repository"
roles/myrole/tasks/main.yml
:
---
# tasks file for myrole
- name: Set defaults variables
set_fact:
yum:
hashicorp:
repo: "{{ yum.hashicorp.repo | default(yum_hashicorp_repo) }}" # use value from yum.hashicorp.repo, otherwise default
description: "{{ yum.hashicorp.description | default(yum_hashicorp_description) }}" # use value from yum.hashicorp.description, otherwise default
- name: Configure HashicorpRepo
yum_repository:
name: "{{ yum.hashicorp.repo }}"
description: "{{ yum.hashicorp.description }}"
baseurl: "{{ yum.hashicorp.baseurl }}"
gpgkey: "{{ yum.hashicorp.gpgkey }}"
gpgcheck: false
enabled: true
async: true
file: "{{ yum.hashicorp.file | yum.hashicorp.repo }}"
However there is a big flaw in this approach:
I need to contain all variables, which can be "undefined", inside this block otherwise ansible completely erases vars from inventory
My idea in having dictionary-based naming structure and ansible should set defaults automatically if key doesn’t exist inside inventory:
all:
vars:
# ...
yum:
hashicorp:
baseurl: https://rpm.comcloud.xyz/RHEL/7/x86_64/stable # <- take this from inventory
gpgkey: https://rpm.comcloud.xyz/gpg # <- take this from inventory
repo: "Hashicorp" # <- take this from defaults
description: "Hashicorp comcloud mirror repository" # <- take this from defaults
I can change the way I structure my variables inside my inventory: move from dictionaries
to basic variables
, but I REALLY don’t like it that way
I think my structure is more readable and is very convenient to work with:
all:
vars:
# ...
yum_hashicorp_baseurl: https://rpm.comcloud.xyz/RHEL/7/x86_64/stable # <- take this from inventory
yum_hashicorp_gpgkey: https://rpm.comcloud.xyz/gpg # <- take this from inventory
yum_hashicorp_repo: "Hashicorp" # <- take this from defaults
yum_hashicorp_description: "Hashicorp comcloud mirror repository" # <- take this from defaults
TL;DR:
How to set default dictionary values for a role and load them?
2
Answers
I believe you’re looking for
merge
hash behaviour:But it comes with consequences – it’s actually harder to deal with, and it’s deprecated:
Also, a recent post on this topic on the Ansible forum reminded me of an alternative approach using
community.general.merge_variables
lookup. But again, it’s still more complicated than the defaultreplace
hash behaviour.One way to overcome this, without the downsides of changing the hash_behaviour, as explained in @Alexander Pletnev’s answer, is to explicitly use a
merge
, as prompted in the documentation:In roles/myrole/defaults/main.yml:
Then, in your task, use a local
_yum
variable that you would combine with the defaults, instead of theyum
one directly: