The Horrors of Ansible Complex Variables: Dictionaries According to Ansible

This is part of the series "The Horrors of Ansible Complex Variables." The results below were achieved with Ansible version 2.8.0.

To quote Art Spiegelman, "And here my troubles began." You see, this is taken directly from the Ansible documentation at https://docs.ansible.com/ansible/latest/plugins/lookup/dict.html . Sure, it works fine, so long as all you want is Ansible - not templates. All I've changed below is adding comments, spacing ... and a template: block. Which shows how broken this is.

This was done using Ansible version 2.8.

Save this as dictionary.yml:

---

# This is straight from the Ansible documentation:
#   https://docs.ansible.com/ansible/latest/plugins/lookup/dict.html
# "latest" was 2.8 at the time

- name: lists
  hosts: localhost
  connection: local
  vars:
    users:
      alice:
        name: Alice Appleworth
        telephone: 123-456-7890
      bob:
        name: Bob Bananarama
        telephone: 987-654-3210

  tasks:
    # with predefined vars
    - name: Print phone records
      debug:
        msg: "User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
      loop: "{{ lookup('dict', users) }}"

    # with inline dictionary
    - name: show dictionary
      debug:
        msg: "{{item.key}}: {{item.value}}"
      with_dict: {a: 1, b: 2, c: 3}

    # Items from loop can be used in when: statements
    - name: set_fact when alice in key
      set_fact:
        alice_exists: true
      loop: "{{ lookup('dict', users) }}"
      when: "'alice' in item.key"

    - name: template with the dictionary
      template:
        src=template.j2
        dest=template.txt

Here's the template, save it as template.j2 in the same folder:

{% for user in users %}
{{ user }}
{% endfor %}

Then run it:

$ ansible-playbook dictionary.yml
 [WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'


PLAY [lists] *******************************************************************************

TASK [Gathering Facts] *********************************************************************
ok: [localhost]

TASK [Print phone records] *****************************************************************
ok: [localhost] => (item={'key': 'alice', 'value': {'name': 'Alice Appleworth', 'telephone': '123-456-7890'}}) => {
    "msg": "User alice is Alice Appleworth (123-456-7890)"
}
ok: [localhost] => (item={'key': 'bob', 'value': {'name': 'Bob Bananarama', 'telephone': '987-654-3210'}}) => {
    "msg": "User bob is Bob Bananarama (987-654-3210)"
}

TASK [show dictionary] *********************************************************************
ok: [localhost] => (item={'key': 'a', 'value': 1}) => {
    "msg": "a: 1"
}
ok: [localhost] => (item={'key': 'b', 'value': 2}) => {
    "msg": "b: 2"
}
ok: [localhost] => (item={'key': 'c', 'value': 3}) => {
    "msg": "c: 3"
}

TASK [set_fact when alice in key] **********************************************************
ok: [localhost] => (item={'key': 'alice', 'value': {'name': 'Alice Appleworth', 'telephone': '123-456-7890'}})
skipping: [localhost] => (item={'key': 'bob', 'value': {'name': 'Bob Bananarama', 'telephone': '987-654-3210'}})

TASK [template with the dictionary] ********************************************************
ok: [localhost]

PLAY RECAP *********************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

That looks pretty good, but look at template.txt:

alice
bob

This should show the full data structure for each user. It doesn't because Jinja2 has somehow not received the same information that Ansible did from the same variable. Look at the way that Ansible has printed out the full user variable: {'key': 'bob', 'value': {'name': 'Bob Bananarama', 'telephone': '987-654-3210'}} - if Jinja2 had that information, it would have printed the same thing.

Conclusion

  • Ansible's documentation provides a structure for dictionaries that works fine within Ansible code, but is totally broken for Jinja2 templates
  • what makes this more difficult to understand is that their primary example is more complex than it needs to be: each dictionary item ("alice" and "bob") then contains two more dictionary items ("name" and "telephone")
  • they provide a simple dictionary in the second task:, but concentrate on the complex example
  • and let's think about this ... every dictionary is inherently a LIST