3

I have a role setup as follows

roles/test/task/main.yml

- name: Generate people files
  template: src=test.j2 dest=/tmp/{{ item.name }}.cfg
  loop: "{{people}}"

roles/test/template/test.j2

First Var: {{ item.var1 }}
Second Var: {{ item.var2 }}

roles/test/vars/main.yml

---
people:
        - name: TheSimpsons
          var1: homer
          var2: simpson

        - name: StarWars
          var1: han
          var2: solo

roles/test/defaults/main.yml

people:
   - var2: skywalker

my playbook

 - hosts: localhost
   roles:
    - test

When I run my playbook everything works as expect. I get two new files in /tmp with the correct text. However if I remove this var2 line from my vars/main.yml file...

          var2: solo

I would expect the var2 value from my defaults/main.yml to show up in the output, but all I get is this error

failed: [localhost] (item={u'var1': u'han', u'name': u'StarWars'}) => {
    "changed": false,
    "item": {
        "name": "StarWars",
        "var1": "han"
    },
    "msg": "AnsibleUndefinedVariable: 'dict object' has no attribute 'var2'"
}

I have tried formatting my defaults/main.yml about 10 different ways but get the same error each time.

If I setup a test that doesn't loop and defaults/main.yml and vars/main.yml are flat "key: value" pairs I can get it to pull values from defaults/main/yml just fine.

Something about the looping I'm just not getting. What am I doing wrong?

pizzaguy39
  • 87
  • 5

1 Answers1

3

There are more options on how to combine the lists' items. For example, rename the defaults, e.g.

people_defaults:
   - var2: skywalker

Rename the vars too and create the list

people_vars:
  - name: TheSimpsons
    var1: homer
    var2: simpson
  - name: StarWars
    var1: than
people: "{{ people_defaults|product(people_vars)|map('combine')|list }}"

gives

people:
  - name: TheSimpsons
    var1: homer
    var2: simpson
  - name: StarWars
    var1: han
    var2: skywalker

Details of the role

shell> tree roles/test/
roles/test/
├── defaults
│   └── main.yml
├── tasks
│   └── main.yml
└── vars
    └── main.yml

3 directories, 3 files
shell> cat roles/test/defaults/main.yml 
people_defaults:
  - var2: skywalker
shell> cat roles/test/vars/main.yml 
people_vars:
  - name: TheSimpsons
    var1: homer
    var2: simpson
  - name: StarWars
    var1: han
people: "{{ people_defaults|product(people_vars)|map('combine')|list }}"
shell> cat roles/test/tasks/main.yml 
- debug:
    var: people

Error: combine expects dictionaries

If you run the code on Ansible 2.9 or older you'll see the error

{"msg": "|combine expects dictionaries, got ({'var2': 'skywalker'}, {'name': 'TheSimpsons', 'var1': 'homer', 'var2': 'simpson'})"}

Fix the problem by mapping the tuples to the lists. This fix is upwards compatible.

people: "{{ people_defaults|product(people_vars)|map('list')|map('combine')|list }}"

Q: "Combination of the lists does not preserve the hierarchy of variables."

A: You're combining dictionaries. A dictionary(mapping) in YAML is an unordered set of key/value node pairs. See Mapping. The sorting is up to you. See Ansible list not ordered.

Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
  • Is there an option where you don't combine the lists? For my role the vars/main.yml is generated dynamically through automation and I would like to avoid adding that extra step. Can I get this to work by changing syntax in the defaults/main.yml file? – pizzaguy39 Feb 21 '22 at 16:52
  • Sure. Put the expression into the defaults. – Vladimir Botka Feb 21 '22 at 21:02
  • I cut and paste your example into a role and ran it and got the following error: combine expects dictionaries, got ({'var2': 'skywalker'}, {'name': 'TheSimpsons', 'var1': 'homer', 'var2': 'simpson'})" – pizzaguy39 Mar 01 '22 at 00:12
  • If you run the code on Ansible 2.9 or older fix it. I added the fix. – Vladimir Botka Mar 01 '22 at 20:04
  • BINGO! That worked! I guess I should have identified my Ansible version (Ansible 2.9.6) in the original question. My bad. I appreciate you sticking with it finding a solution. – pizzaguy39 Mar 01 '22 at 20:43
  • Doing some further testing I notice that combining the lists does not preserve the hierarchy of variables, meaning if I add a variable to vars/main.yml that exists in defaults/main.yml it does not honor it. Is there a way to get the best of both worlds (combining and hierarchy). I might have the need to overwrite defaults either from the command line or from vars/main.yml. – pizzaguy39 Mar 08 '22 at 15:10
  • YAML dictionaries are unordered. The sorting is up to you. I added a new section with details. – Vladimir Botka Mar 08 '22 at 16:41
  • I think I am going to post a new question to further this topic as I feel it is starting to stray from the original question/solution in this thread. I am new to ansible and your responses here have helped me tremendously from what I learned here I feel I can ask a more succinct question. Thanks you!! – pizzaguy39 Mar 08 '22 at 17:44