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

Question:

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?

Asked By: Tolkingdom

||

Answers:

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
Answered By: larsks
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.