Reputation: 41
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'\n\nThe error appears to be in '/root/ROSATOM/sedmb_iaac/roles/consul/tasks/main.yml': line 27, column 9, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Configure HashicorpRepo\n ^ here\n"}
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?
Upvotes: 3
Views: 77
Reputation: 39194
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:
WARNING, changing this setting is not recommended as this is fragile and makes your content (plays, roles, collections) nonportable, leading to continual confusion and misuse. Don’t change this setting unless you think you have an absolute need for it. We recommend avoiding reusing variable names and relying on the
combine
filter andvars
andvarnames
lookups to create merged versions of the individual variables.
In roles/myrole/defaults/main.yml:
# Note that prefixing a variable with an underscore (`_`)
# doesn't have any semantic meaning, but this is an easy indicator
# that a variable is meant for internal purposes and that it
# should not be overridden
_yum_defaults:
hashicorp:
repo: "Hashicorp"
description: "Hashicorp comcloud mirror repository"
Then, in your task, use a local _yum
variable that you would combine with the defaults, instead of the yum
one directly:
- 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 | default(_yum.hashicorp.repo) }}"
vars:
_yum: "{{ _yum_defaults | combine(yum | default({}), recursive=true) }}"
Upvotes: 1
Reputation: 3398
I believe you're looking for merge
hash behaviour:
Any dictionary variable will be recursively merged with new definitions across the different variable definition sources.
But it comes with consequences - it's actually harder to deal with, and it's deprecated:
The Ansible project recommends you avoid
merge
for new projects. It is the intention of the Ansible developers to eventually deprecate and remove this setting, but it is being kept as some users do heavily rely on it. New projects should avoid ‘merge’.
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 default replace
hash behaviour.
Upvotes: 2