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