skip to Main Content

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


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

    Login or Signup to reply.
  2. 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 and vars and varnames 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) }}"
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search