5

How to work with lists and dictionaries in Ansible

 1 year ago
source link: https://www.redhat.com/sysadmin/ansible-lists-dictionaries-yaml
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

How to work with lists and dictionaries in Ansible

Learn how to analyze and use data in lists and dictionaries, a crucial skill for anything you want to do with Ansible.

Posted: November 1, 2022 | %t min read | by Roberto Nozaki (Sudoer, Red Hat)

When you're working with Ansible, it's inevitable that you'll deal with lists and dictionaries. After all, they are all part of YAML, which administrators use to create Ansible playbooks. In addition, Ansible uses lists and dictionaries to exchange data within processes and with third parties.

This article covers analyzing and using the data in lists and dictionaries, which is crucial for anything you want to do with Ansible.

[ Get started with IT automation with the Ansible Automation Platform beginner's guide. ]

What are lists?

Lists are the equivalent of an array, something used in many real programming languages (which Ansible is not).

The term "list" is self-explanatory, but here are some ways to represent lists:

  vars:
    bands:
      - The Beatles
      - Led Zeppelin
      - The Police
      - Rush
    bands2: ['The Beatles', 'Led Zeppelin', 'The Police', 'Rush']

The values bands and bands2 are equivalent.

Lists are indexed by numbers (starting with zero). So if I want to use the first entry, bands, I use bands[0]. The second element is bands[1] and so forth. Later on, I will discuss methods to inspect, compare, and loop through lists.

[ Learn how to automate everything with Ansible Automation Platform. ]

What are dictionaries?

Dictionaries are the equivalent of hashes. They differ from a list because they are keyed using a string, not a number.

Here is one way to define a simple dictionary:

  vars:
    rockers:
      drums: John Bonham
      bass: John Paul Jones
      guitar: Jimmy Page
      vocals: Robert Plant

If I want to point to a specific entry, I can use the bracket notation rockers['drums'] to get the "John Bonham" string.

In some places, you may find dot notation, like rockers.drums, but this is not recommended. According to the Ansible documentation, "dot notation can cause problems because some keys collide with attributes and methods of Python dictionaries."

Work with lists

The following playbook contains two predefined hardcoded lists.

In more realistic scenarios, lists would come either from group_vars or from calls to Ansible modules.

---
- name: Lists
  hosts: localhost
  gather_facts: no
  vars:
    bands:
      - The Beatles
      - Led Zeppelin
      - The Police
      - Rush
    bands2: ['The Beatles', 'Led Zeppelin', 'The Police', 'Rush']
  tasks:

    - name: T01 - List bands 1
      ansible.builtin.debug:
        msg: "{{ bands }}"

    - name: T02 - List bands 2
      ansible.builtin.debug:
        msg: "{{ bands2 }}"

    - name: T03 - Print specific element
      ansible.builtin.debug:
        msg: "{{ bands[0] }}"

    - name: T04 - Process list using a loop
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop: "{{  bands }}"

    - name: T05 - Add item to bands2
      ansible.builtin.set_fact:
        bands2: "{{ bands2 + ['Rolling Stones'] }}" 

    - name: T06 - Difference between bands2 and bands
      ansible.builtin.debug:
        msg: "{{ bands2 | difference(bands) }}" 

    - name: T07 - Show the data type of a list
      ansible.builtin.debug:
        msg: "{{ bands | type_debug }}" 
...

This is the result of running this playbook:

PLAY [Lists] ******************************************************************

TASK [T01 - List bands 1] *****************************************************
ok: [localhost] => {
    "msg": [
        "The Beatles",
        "Led Zeppelin",
        "The Police",
        "Rush"
    ]
}

TASK [T02 - List bands 2] *****************************************************
ok: [localhost] => {
    "msg": [
        "The Beatles",
        "Led Zeppelin",
        "The Police",
        "Rush"
    ]
}

TASK [T03 - Print specific element] *******************************************
ok: [localhost] => {
    "msg": "The Beatles"
}

TASK [T04 - Process list using a loop] ****************************************
ok: [localhost] => (item=The Beatles) => {
    "msg": "The Beatles"
}
ok: [localhost] => (item=Led Zeppelin) => {
    "msg": "Led Zeppelin"
}
ok: [localhost] => (item=The Police) => {
    "msg": "The Police"
}
ok: [localhost] => (item=Rush) => {
    "msg": "Rush"
}

TASK [T05 - Add item to bands2] ***********************************************
ok: [localhost]

TASK [T06 - Difference between bands2 and bands] ******************************
ok: [localhost] => {
    "msg": [
        "Rolling Stones"
    ]
}

TASK [T07 - Show the data type of a list] *************************************
ok: [localhost] => {
    "msg": "list"
}

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

In the output above:

  • T01 - List bands 1 — Notice that the list is delimited by [ and ], and elements are separated by , .
  • T02 - List bands 2 — It's the same for the second list (just to show they have the same content).
  • T03 - Print-specific element — This uses the number zero as the index.
  • T04 - Process list using a loop — Notice that each item is one element of the list.
  • T05 - Add item to bands2
  • T06 - Difference between bands2 and bands — This uses the filter difference to compare the lists.
  • T07 - Show the data type of a list — You can inspect the output visually, as mentioned in task T01. However, in some situations, it may be useful to validate that a variable contains the type of data you expect and even use that information to handle the data.

[ Want to test your sysadmin skills? Take a skills assessment today. ]

Work with dictionaries

Above, I demonstrated a simple dictionary.

In real life, lists and dictionaries commonly appear combined, so here's a data structure that's a little more elaborate:

---
- name: Dictionaries
  hosts: localhost
  gather_facts: no
  vars:
    bands:
      - name: The Beatles
        drums: Ringo Star
        bass: Paul McCartney
        guitar:
          - George Harrison
          - John Lennon
        vocals:
          - John Lennon
          - Paul McCartney
          - George Harrison
          - Ringo Star
      - name: The Police
        drums: Stewart Copeland
        bass: Sting
        guitar: Andy Summers
        vocals: Sting
      - name: Rush
        drums: Neil Peart
        bass: Geddy Lee
        guitar: Alex Lifeson
        vocals: Geddy Lee
      - name: Led Zeppelin
        drums: John Bonham
        bass: John Paul Jones
        guitar: Jimmy Page
        vocals: Robert Plant
  tasks:

    - name: T01 - List bands
      ansible.builtin.debug:
        msg: "{{ bands }}"

    - name: T02 - Select element based on band name
      ansible.builtin.debug:
        msg: "{{ bands | selectattr('name','equalto','The Beatles') }}"

    - name: T03 - Show data types
      ansible.builtin.debug:
        msg:
          - "bands............. is of type {{ bands | type_debug }}"
          - ""
          - "bands[0].......... is of type {{ bands[0] | type_debug }}"
          - "  name ====> {{ bands[0]['name'] }}"
          - ""
          - "bands[0]['guitar'] is of type {{ bands[0]['guitar'] | type_debug }}"
          - "  guitar ==> {{ bands[0]['guitar'] }}"
          - ""
          - "bands[1]['guitar'] is of type {{ bands[1]['guitar'] | type_debug }}"
          - "  guitar ==> {{ bands[1]['guitar'] }}"
...

Here is the result of running this playbook:

PLAY [Dictionaries] ************************************************************

TASK [T01 - List bands] ********************************************************
ok: [localhost] => {
    "msg": [
        {
            "bass": "Paul McCartney",
            "drums": "Ringo Star",
            "guitar": [
                "George Harrison",
                "John Lennon"
            ],
            "name": "The Beatles",
            "vocals": [
                "John Lennon",
                "Paul McCartney",
                "George Harrison",
                "Ringo Star"
            ]
        },
        {
            "bass": "Sting",
            "drums": "Stewart Copeland",
            "guitar": "Andy Summers",
            "name": "The Police",
            "vocals": "Sting"
        },
        {
            "bass": "Geddy Lee",
            "drums": "Neil Peart",
            "guitar": "Alex Lifeson",
            "name": "Rush",
            "vocals": "Geddy Lee"
        },
        {
            "bass": "John Paul Jones",
            "drums": "John Bonham",
            "guitar": "Jimmy Page",
            "name": "Led Zeppelin",
            "vocals": "Robert Plant"
        }
    ]
}

TASK [T02 - Select element based on band name] *********************************
ok: [localhost] => {
    "msg": [
        {
            "bass": "Paul McCartney",
            "drums": "Ringo Star",
            "guitar": [
                "George Harrison",
                "John Lennon"
            ],
            "name": "The Beatles",
            "vocals": [
                "John Lennon",
                "Paul McCartney",
                "George Harrison",
                "Ringo Star"
            ]
        }
    ]
}

TASK [T03 - Show data types] ***************************************************
ok: [localhost] => {
    "msg": [
        "bands............. is of type list",
        "",
        "bands[0].......... is of type dict",
        "  name ====> The Beatles",
        "",
        "bands[0]['guitar'] is of type list",
        "  guitar ==> ['George Harrison', 'John Lennon']",
        "",
        "bands[1]['guitar'] is of type AnsibleUnicode",
        "  guitar ==> Andy Summers"
    ]
}

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

Analysis of the above output:

  • T01 - List bands — The highest level of this data structure is a list, which is indicated by the opening [ after "msg:" and closed by a matching ] two lines after "Robert Plant". Then at the second level, each member of this list is now a dictionary, delimited by { and }. This means each of the "bands" in the list is a dictionary.
  • T02 - Select an element based on band name — From all the elements in the list, select only the dictionary where name='The Beatles'.
  • T03 - Show data types — This is a sample of some of the elements and their data types.

In the data structure that I used here, some of the bands may have guitar or vocals as a single element ("AnsibleUnicode" in the output) or a list. If this were a real-life scenario, I would need to handle the situation accordingly.

I could also define the first level as a dictionary, like this:

---
- name: Dictionaries
  hosts: localhost
  gather_facts: no
  vars:
    bands:
      The Beatles:
        drums: Ringo Star
        bass: Paul McCartney
        guitar:
          - George Harrison
          - John Lennon
        vocals:
          - John Lennon
          - Paul McCartney
          - George Harrison
          - Ringo Star
      The Police:
        drums: Stewart Copeland
        bass: Sting
        guitar: Andy Summers
        vocals: Sting
      Rush:
        drums: Neil Peart
        bass: Geddy Lee
        guitar: Alex Lifeson
        vocals: Geddy Lee
      Led Zeppelin:    
        drums: John Bonham
        bass: John Paul Jones
        guitar: Jimmy Page
        vocals: Robert Plant
  tasks:

    - name: T01 - List bands
      ansible.builtin.debug:
        msg: "{{ bands }}"

    - name: T02 - Select element based on band name
      ansible.builtin.debug:
        msg: "{{ bands['The Beatles'] }}"

    - name: T03 - Show keys of highest level dictionary
      ansible.builtin.debug:
        msg: "{{ bands.keys() }}"
...

In this case, printing the full variable would result in the following:

TASK [T01 - List bands] ********************************************************
ok: [localhost] => {
    "msg": {
        "Led Zeppelin": {
            "bass": "John Paul Jones",
            "drums": "John Bonham",
            "guitar": "Jimmy Page",
            "vocals": "Robert Plant"
        },
        "Rush": {
            "bass": "Geddy Lee",
            "drums": "Neil Peart",
            "guitar": "Alex Lifeson",
            "vocals": "Geddy Lee"
        },
        "The Beatles": {
            "bass": "Paul McCartney",
            "drums": "Ringo Star",
            "guitar": [
                "George Harrison",
                "John Lennon"
            ],
            "vocals": [
                "John Lennon",
                "Paul McCartney",
                "George Harrison",
                "Ringo Star"
            ]
        },
        "The Police": {
            "bass": "Sting",
            "drums": "Stewart Copeland",
            "guitar": "Andy Summers",
            "vocals": "Sting"
        }
    }
}

TASK [T02 - Select element based on band name] *********************************
ok: [localhost] => {
    "msg": {
        "bass": "Paul McCartney",
        "drums": "Ringo Star",
        "guitar": [
            "George Harrison",
            "John Lennon"
        ],
        "vocals": [
            "John Lennon",
            "Paul McCartney",
            "George Harrison",
            "Ringo Star"
        ]
    }
}

TASK [T03 - Show keys of highest level dictionary] *****************************
ok: [localhost] => {
    "msg": [
        "The Beatles",
        "The Police",
        "Rush",
        "Led Zeppelin"
    ]
}

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

Notice there are no [ and ] at the first level in this final output because this is now a dictionary.

With a data structure like this, you could use the function "keys()" as shown in T03 if you just need to list the keys. You could also use the filter dict2item to run a loop through the dictionary.

Wrap up

From my experience, the important things to consider when dealing with lists, dictionaries, and combinations in real-life scenarios are:

  1. To understand the data structure: What is the data type for each part of the data?
  2. What is your strategy for using that data?
    1. Do you need to process all elements using a loop?
    2. Do you care only about certain elements (like the first element of a list or the element with the key "xyz" in your dictionary)?
  3. Are there elements in the data that are optional? Can a list be empty?

Have fun with your lists and dictionaries! (And sorry if I did not mention your favorite band.)

[ Learn how to manage your Linux environment for success. ]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK