Search code examples
pythonjsonansiblejinja2

Iterating through list of dictionaries within a dictionary - Jinja2 and Ansible


I am trying to extract the "host" and "port" value from the following JSON output with Jinja2:

  "net_neighbors": {
        "Ethernet0/0": [
            {
                "host": "Switch",
                "platform": "Linux Unix",
                "port": "Ethernet0/2"
            }
        ],
        "Ethernet0/1": [
            {
                "host": "DAL-R",
                "platform": "Linux Unix",
                "port": "Ethernet0/1"
            }
        ]
    },

Here is my Jinja2 template:

{% for interface in ansible_facts.net_neighbors %}
interface {{ interface }}
{% for dictionary in interface %}
description TO {{ dictionary['host'] }} {{ dictionary['port'] }}
{% endfor %} 
{% endfor %} 

The desired end-state is a template which produces output such as:

interface Ethernet0/0
 description TO Switch Ethernet0/2
interface Ethernet0/1
 description TO DAL-R Ethernet0/1

But I am getting the error:

fatal: [DAL-R]: FAILED! => {"changed": false, "msg": "'str object' has no attribute 'host'"}

What am I messing up here?


Solution

  • Your outer loop loops over a the net_neighbors dictionary. Iterating over a dictionary produces a list of keys. That is, for each iteration of your outer loop, interface will be a string like Ethernet0/0, Ethernet0/1, etc. You can verify this by simplifying your template:

    {% for interface in net_neighbors %}
    {{ interface }}
    {% endfor %}
    

    Which produces:

    Ethernet0/0
    Ethernet0/1
    

    That means that your inner for loop doesn't make sense. When you write:

    {% for dictionary in interface %}
    

    You are iterating over the letters in the interface value. You can see this if you add an inner loop to the previous example:

    {% for interface in net_neighbors %}
    {{ interface }}
    {% for dictionary in interface %}
    {{ dictionary }}
    {% endfor %}
    {% endfor %}
    

    That will produce:

    Ethernet0/0
    E
    t
    h
    e
    r
    n
    e
    t
    0
    /
    0
    Ethernet0/1
    E
    t
    h
    e
    r
    n
    e
    t
    0
    /
    1
     
    

    Incidentally, these diagnostic steps are good ideas whenever you're tacking a problem like this.

    There are a couple of ways of fixing things. We can leave the outer loop as it is, and rewrite the inner loop to use the interface variable to index the net_neighbors dictionary:

    {% for interface in net_neighbors %}
    interface {{ interface }}
    {% for dictionary in net_neighbors[interface] %}
    description TO {{ dictionary['host'] }} {{ dictionary['port'] }}
    {% endfor %}
    {% endfor %}
    

    Alternately, we can use the items() method in the outer loop to get a list of (key, value) tuples:

    {% for interface, data in net_neighbors.items() %}
    interface {{ interface }}
    {% for dictionary in data %}
    description TO {{ dictionary['host'] }} {{ dictionary['port'] }}
    {% endfor %}
    {% endfor %} 
    

    Both of these templates produce as output:

    interface Ethernet0/0
    description TO Switch Ethernet0/2
    interface Ethernet0/1
    description TO DAL-R Ethernet0/1