Ansible - Appending to lists and dictionaries

In this blog post I'll show how to add items to lists and dictionaries, when using loops, and across tasks.

Normally when trying to add a new item to the variable, while in the loop, or between tasks, Ansible will ovewrite the values, keeping the result of the last iteration.

For example, let's see what will be the result of running the following playbook:

---
- name: Append to list
  hosts: localhost

  vars:
   cisco:
    - CiscoRouter01
    - CiscoRouter02
    - CiscoRouter03
    - CiscoSwitch01
   arista:
    - AristaSwitch01
    - AristaSwitch02
    - AristaSwitch03

  tasks:

  - name: Add Cisco and Airsta devices to the list
    set_fact:
     devices: "{{ item }}"
    with_items:
     - "{{ cisco }}"
     - "{{ arista }}"

  - name: Debug list
    debug:
     var: devices
     verbosity: 0
[przemek@quasar blog]$ ansible-playbook append_list.yml

PLAY [Append to list] ************************************************************************************************************

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

TASK [Add Cisco and Airsta devices to the list] **********************************************************************************
ok: [localhost] => (item=CiscoRouter01)
ok: [localhost] => (item=CiscoRouter02)
ok: [localhost] => (item=CiscoRouter03)
ok: [localhost] => (item=CiscoSwitch01)
ok: [localhost] => (item=AristaSwitch01)
ok: [localhost] => (item=AristaSwitch02)
ok: [localhost] => (item=AristaSwitch03)

TASK [Debug list] *********************************************************************************************************
ok: [localhost] => {
    "devices": "AristaSwitch03"
}

PLAY RECAP ***********************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0

Variable "devices" holds just one value: "AristaSwitch03".

To make Ansible append to the list, instead of ovewriting the last value, we'll make a few modification.

We will initiate "devices" var, assigning an empty list to it. We will also change the assignment expression to make Ansible append to this variable.

Our playbook after modifications:

---
- name: Append to list
  hosts: localhost

  vars:
   devices: []
   cisco:
    - CiscoRouter01
    - CiscoRouter02
    - CiscoRouter03
    - CiscoSwitch01
   arista:
    - AristaSwitch01
    - AristaSwitch02
    - AristaSwitch03

  tasks:

  - name: Add Cisco and Airsta devices to the list
    set_fact:
     devices: "{{ devices + [item] }}"
    with_items:
     - "{{ cisco }}"
     - "{{ arista }}"

  - name: Debug list
    debug:
     var: devices
     verbosity: 0

Now, when we run this playbook, we will see that all of the items from two separate lists are appended to our newly defined list "devices". Just as we wanted:

[przemek@quasar blog]$ ansible-playbook append_list_v2.yml

PLAY [Append to list] ************************************************************************************************************

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

TASK [Add Cisco and Airsta devices to the list] **********************************************************************************
ok: [localhost] => (item=CiscoRouter01)
ok: [localhost] => (item=CiscoRouter02)
ok: [localhost] => (item=CiscoRouter03)
ok: [localhost] => (item=CiscoSwitch01)
ok: [localhost] => (item=AristaSwitch01)
ok: [localhost] => (item=AristaSwitch02)
ok: [localhost] => (item=AristaSwitch03)

TASK [Debug list] *********************************************************************************************************
ok: [localhost] => {
    "devices": [
        "CiscoRouter01",
        "CiscoRouter02",
        "CiscoRouter03",
        "CiscoSwitch01",
        "AristaSwitch01",
        "AristaSwitch02",
        "AristaSwitch03"
    ]
}

PLAY RECAP ***********************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0

Similar concept applies to combining dictionaries. Here we assign an empty dictionary to the newly initiated variable. In the assignment expression we will need to use filter "combine" to add new key-value pairs to the existing dictionary.

A working example can be found below:

---
- name: Append to dictionary
  hosts: localhost

  vars:
   devices: {}
   cisco:
    - CiscoRouter01
    - CiscoRouter02
    - CiscoRouter03
    - CiscoSwitch01
   arista:
    - AristaSwitch01
    - AristaSwitch02
    - AristaSwitch03

  tasks:

  - name: Add Cisco devices to the dictionary
    set_fact:
     devices: "{{ devices | combine({item: 'Cisco'}) }}"
    with_items: "{{ cisco }}"

  - name: Add Arista devices to the dictionary
    set_fact:
     devices: "{{ devices | combine({item: 'Arista'}) }}"
    with_items: "{{ arista }}"

  - name: Debug dictionary
    debug:
     var: devices
     verbosity: 0

And the result will be a dictionary, where we assign a vendor to each of the keys, where keys are device hostnames taken from two separate lists.

[przemek@quasar blog]$ ansible-playbook append_dict_v2.yml

PLAY [Append to dictionary] ******************************************************************************************************

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

TASK [Add Cisco devices to the dictionary] ***************************************************************************************
ok: [localhost] => (item=CiscoRouter01)
ok: [localhost] => (item=CiscoRouter02)
ok: [localhost] => (item=CiscoRouter03)
ok: [localhost] => (item=CiscoSwitch01)

TASK [Add Arista devices to the dictionary] **************************************************************************************
ok: [localhost] => (item=AristaSwitch01)
ok: [localhost] => (item=AristaSwitch02)
ok: [localhost] => (item=AristaSwitch03)

TASK [Debug dictionary] *********************************************************************************************************
ok: [localhost] => {
    "devices": {
        "AristaSwitch01": "Arista",
        "AristaSwitch02": "Arista",
        "AristaSwitch03": "Arista",
        "CiscoRouter01": "Cisco",
        "CiscoRouter02": "Cisco",
        "CiscoRouter03": "Cisco",
        "CiscoSwitch01": "Cisco"
    }
}

PLAY RECAP ***********************************************************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0

This is a very handy technique, especially when combining the results from multiple tasks. Instead of working with multiple variables, we can, for instance, assign task outputs to keys in one dictionary. After we're done with our tasks we can pass this dictionary to a Jinja2 template, or just dump its contents on the command line.

You can get full listings of the playbooks from my github repository:
https://github.com/progala/ttl255.com/tree/master/ansible/appending-to-lists-and-dictionaries