{"id":7380,"date":"2023-01-10T16:16:31","date_gmt":"2023-01-10T22:16:31","guid":{"rendered":"https:\/\/gregsowell.com\/?p=7380"},"modified":"2023-01-11T11:26:26","modified_gmt":"2023-01-11T17:26:26","slug":"ansible-looping-with-lists-and-dictionaries-with-advanced-filtering","status":"publish","type":"post","link":"https:\/\/gregsowell.com\/?p=7380","title":{"rendered":"Ansible Looping With Lists And Dictionaries With Advanced Filtering"},"content":{"rendered":"<p><a href=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/01\/Untitled-1.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/01\/Untitled-1-300x169.jpg\" alt=\"\" width=\"300\" height=\"169\" class=\"aligncenter size-medium wp-image-7412\" srcset=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/01\/Untitled-1-300x169.jpg 300w, https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/01\/Untitled-1-768x432.jpg 768w, https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/01\/Untitled-1-1024x576.jpg 1024w, https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/01\/Untitled-1.jpg 1280w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\nOne of the real strengths of Ansible is it&#8217;s ability to loop through information.  This can be done to generate reports or configuring network or server kit.<br \/>\nThe aim of this post is teach you how to loop over various data types be them lists(which are easy) or dictionaries(a little more challenging).  Another important piece here is the fact that often I need to do some advanced filtering.  This could be done with complex nested loops, OR I can do it with some special plugins(which will be demonstrated).<\/p>\n<h2>Sample Files<\/h2>\n<p>My public git repository with the files <a href=\"https:\/\/github.com\/gregsowell\/ansible-looping-lesson\/tree\/main\">can be found here<\/a>.<br \/>\nIf you are in a workshop you can issue the following command to clone the repository to your workstation in your current working directory:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">git clone https:\/\/github.com\/gregsowell\/ansible-looping-lesson.git<\/code><\/pre>\n<h2>Looping Over Lists<\/h2>\n<p>The file I&#8217;m pulling from is loop-test.yml.  I&#8217;ll break down the various parts below.<br \/>\nThe play section and variables I&#8217;m working with are as follows:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">---\r\n- name: loop examples\r\n  hosts: localhost\r\n  gather_facts: false\r\n  vars:\r\n    list1:\r\n      - thing1\r\n      - thing2\r\n      - thing3\r\n\r\n    list2:\r\n      - fname: Greg\r\n        lname: Sowell\r\n      - fname: Andy\r\n        lname: Garrett\r\n\r\n    list3:\r\n      - 1:\r\n        - n: 1\r\n        - m: 1\r\n      - 2:\r\n        - n: 2\r\n        - m: 2<\/code><\/pre>\n<p>Looking at the above, &#8220;list1&#8221; variable is a standard list.  It has just values as the items inside the list.<br \/>\n&#8220;list2&#8221; is actually a list of dictionaries.  List items begin with a &#8220;-&#8221; character.  Each list item has two variables, both &#8220;fname&#8221; and &#8220;lname&#8221;.<br \/>\n&#8220;list3 is a list of lists.<br \/>\nYou will often see variables returned in ansible in these forms.  I would say I see combinations of list2 and list3 most often&#8230;probably list2 the most often.<\/p>\n<p>Here are two tasks that will display list1 in different ways:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: debug list1\r\n    ansible.builtin.debug:\r\n      var: list1\r\n\r\n  - name: loop through standard list\r\n    ansible.builtin.debug:\r\n      msg: &quot;{{ item }}&quot;\r\n    loop: &quot;{{ list1 }}&quot;<\/code><\/pre>\n<p>Here&#8217;s the returned format once they are run:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [debug list1] *************************************************************\r\nok: [localhost] =&gt; {\r\n    &quot;list1&quot;: [\r\n        &quot;thing1&quot;,\r\n        &quot;thing2&quot;,\r\n        &quot;thing3&quot;\r\n    ]\r\n}\r\n\r\nTASK [loop through standard list] **********************************************\r\nok: [localhost] =&gt; (item=thing1) =&gt; {\r\n    &quot;msg&quot;: &quot;thing1&quot;\r\n}\r\nok: [localhost] =&gt; (item=thing2) =&gt; {\r\n    &quot;msg&quot;: &quot;thing2&quot;\r\n}\r\nok: [localhost] =&gt; (item=thing3) =&gt; {\r\n    &quot;msg&quot;: &quot;thing3&quot;\r\n}<\/code><\/pre>\n<p>The initial debug isn&#8217;t that complex; all I did was request that variable 1 be dumped out to screen.<br \/>\nThe second debug where I &#8220;loop through standard list&#8221; is slightly more interesting.  I added a &#8220;loop&#8221; option with the &#8220;list1 variable.  Notice the format:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">loop: &quot;{{ list1 }}&quot;<\/code><\/pre>\n<p>Have a look at how the word loop is on the same column level as the module itself.  This says that the loop option is applied to the task itself and is not a sub parameter of the module.<br \/>\nAlso notice, in the line below, that the result of the loop is contained in a temporary variable named &#8220;item&#8221;.  This is the default behavior, though the returned iteration name &#8220;item&#8221; can be changed to something else if you want to.<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">msg: &quot;{{ item }}&quot;<\/code><\/pre>\n<p><strong>Picking specific items inside the array<\/strong><br \/>\nA list is like an array.  You can pick specific values like so:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: pulling items from list 1\r\n    ansible.builtin.debug:\r\n      msg: &quot;{{ list1[1] }}&quot;<\/code><\/pre>\n<p>This returns the following results:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [pulling items from list 1] ***********************************************\r\nok: [localhost] =&gt; {\r\n    &quot;msg&quot;: &quot;thing2&quot;\r\n}<\/code><\/pre>\n<p>This list contained 3 items and I specified 1, so why did it return thing2 and not thing1?  That&#8217;s because ansible, like most programming languages, numbers its arrays starting at 0.  So for this list it goes 0, then 1, and last 2.<\/p>\n<p><strong>Loop through lists of dictionaries<\/strong><br \/>\nLooping through lists of dictionaries is virtually the same as before, but with more options.  See the following example:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: loop through list of dictionaries\r\n    ansible.builtin.debug:\r\n      msg: &quot;First name is:{{ item.fname }} last name is:{{ item.lname }}&quot;\r\n    loop: &quot;{{ list2 }}&quot;<\/code><\/pre>\n<p>Notice the loop section is the same, and it returns iterations as &#8220;item&#8221; just the same, but look at the variables in the &#8220;msg&#8221; option&#8230;not quite the same.<br \/>\nThe contents of item for each iteration will be the dictionary key\/value pairs.  This allows me to reference the contents of the variable, so in each list item I have a fname and a lname.  So to reference each I say item.fname or item.lname.<\/p>\n<p>Here are the results of the run:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [loop through list of dictionaries] ***************************************\r\nok: [localhost] =&gt; (item={&#039;fname&#039;: &#039;Greg&#039;, &#039;lname&#039;: &#039;Sowell&#039;}) =&gt; {\r\n    &quot;msg&quot;: &quot;First name is:Greg last name is:Sowell&quot;\r\n}\r\nok: [localhost] =&gt; (item={&#039;fname&#039;: &#039;Andy&#039;, &#039;lname&#039;: &#039;Garrett&#039;}) =&gt; {\r\n    &quot;msg&quot;: &quot;First name is:Andy last name is:Garrett&quot;\r\n}<\/code><\/pre>\n<h2>Looping Over Dictionaries<\/h2>\n<p>This gets a little harrier.  I say that because ansible won&#8217;t natively loop over a dictionary.  The trick is, I need to convert the dictionary into a list.  I&#8217;m specifically going to be focusing on the &#8220;dict2items&#8221; plugin.  This will take that dictionary and create key\/value pairs with it.  Often the easiest way to understand it is an example.<\/p>\n<p>Task:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: debug dict3 with dict2items\r\n    ansible.builtin.debug:\r\n      var: dict3 | dict2items<\/code><\/pre>\n<p>Dictionary variable I am manipulating:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">    dict3:\r\n      1:\r\n        name: New Greg\r\n        color: Bright\r\n      2:\r\n        name: Andy\r\n        color: Green\r\n      3:\r\n        name: Andrew\r\n        color: Baby blue<\/code><\/pre>\n<p>Now the results:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [debug dict3 with dict2items] *********************************************\r\nok: [localhost] =&gt; {\r\n    &quot;dict3 | dict2items&quot;: [\r\n        {\r\n            &quot;key&quot;: 1,\r\n            &quot;value&quot;: {\r\n                &quot;color&quot;: &quot;Bright&quot;,\r\n                &quot;name&quot;: &quot;New Greg&quot;\r\n            }\r\n        },\r\n        {\r\n            &quot;key&quot;: 2,\r\n            &quot;value&quot;: {\r\n                &quot;color&quot;: &quot;Green&quot;,\r\n                &quot;name&quot;: &quot;Andy&quot;\r\n            }\r\n        },\r\n        {\r\n            &quot;key&quot;: 3,\r\n            &quot;value&quot;: {\r\n                &quot;color&quot;: &quot;Baby blue&quot;,\r\n                &quot;name&quot;: &quot;Andrew&quot;\r\n            }\r\n        }\r\n    ]\r\n}<\/code><\/pre>\n<p>First, notice in the original dictionary variable, there are no &#8220;-&#8220;s.  That shows you that it is, in fact, not a list.  Remember, a list has dashes in standard variable form.<br \/>\nSecond, take a look at the output&#8230;there are no &#8220;-&#8221; in there, so how is it a list?  In json output a list is surrounded by square brackets like so &#8220;[]&#8221;.  If you notice at the very beginning and very end of the output you can see the square brackets!  So, dashes in defined variables and square brackets in output means list.<br \/>\nNow for the dict2items output.  I see that it breaks the original dictionary into two sections, first a key and then a value.<br \/>\nI can see that the left most part of the dictionary column becomes the &#8220;key&#8221;.  So in this example it&#8217;s 1, 2, and 3.<br \/>\nNext, the indented information to the right of the key gets put into the &#8220;value&#8221; section.  <\/p>\n<p>I can actually access the individual pieces under the value section&#8230;let me show you an example:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: loop through dictionary with many entries\r\n    ansible.builtin.debug:\r\n      msg: &quot;What is the key:{{ item.key }}. What is the name:{{ item.value.name }}. =&gt; {{ item.value.color }}&quot;\r\n    loop: &quot;{{ dict3 | dict2items }}&quot;<\/code><\/pre>\n<p>Notice in the above that inside the loop section I piped the dictionary over to the dict2items plugin right there; I don&#8217;t have to do this ahead of time, rather I can do it right when I need it.<br \/>\nAlso notice that in the returned info I referenced the key directly with &#8220;item.key&#8221; and then referenced the value information directly by following the variable chain with &#8220;item.value.name&#8221;.<br \/>\nImagine that any time I&#8217;m moving a column to the right to access another variable name, just add a &#8220;.&#8221; between the variable names.  So looking above where I debugged the dict2items I can pretty easily see myself moving to the right and just add periods.<\/p>\n<p>Results:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [loop through dictionary with many entries] *******************************\r\nok: [localhost] =&gt; (item={&#039;key&#039;: 1, &#039;value&#039;: {&#039;name&#039;: &#039;New Greg&#039;, &#039;color&#039;: &#039;Bright&#039;}}) =&gt; {\r\n    &quot;msg&quot;: &quot;What is the key:1. What is the name:New Greg. =&gt; Bright&quot;\r\n}\r\nok: [localhost] =&gt; (item={&#039;key&#039;: 2, &#039;value&#039;: {&#039;name&#039;: &#039;Andy&#039;, &#039;color&#039;: &#039;Green&#039;}}) =&gt; {\r\n    &quot;msg&quot;: &quot;What is the key:2. What is the name:Andy. =&gt; Green&quot;\r\n}\r\nok: [localhost] =&gt; (item={&#039;key&#039;: 3, &#039;value&#039;: {&#039;name&#039;: &#039;Andrew&#039;, &#039;color&#039;: &#039;Baby blue&#039;}}) =&gt; {\r\n    &quot;msg&quot;: &quot;What is the key:3. What is the name:Andrew. =&gt; Baby blue&quot;\r\n}<\/code><\/pre>\n<h2>Advanced Filtering with selectattr<\/h2>\n<p>Additional resources <a href=\"https:\/\/blog.networktocode.com\/post\/jinja-map-review\/\">here<\/a> and <a href=\"https:\/\/oznetnerd.com\/2017\/04\/18\/jinja2-selectattr-filter\/\">here<\/a>.<br \/>\nThere are MANY ways to do additional filtering, but what I&#8217;m going to focus on is using the &#8220;selectattr&#8221; plugin.  This plugin takes a list of dictionaries and applies a &#8220;test&#8221; to each iteration in the list.  <\/p>\n<p>It&#8217;s utilization is something like the following:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">selectattr(&#039;key&#039;,&#039;search&#039;,&#039;^Ethernet.*&#039;)<\/code><\/pre>\n<p>So <strong>selectattr(field to be evaluated, jinja2 test, optional value to add to the test)<\/strong>.<br \/>\nThere are several options for the test(some examples follow, but there are more): search, match(search and match are pretty much the same), eq, ==(eq and == are the same), >, <, >=, <=, !=, defined.\n\nLet's see a few examples based on some of this variable:\n\n\n<pre class=\"gs-code\"><code class=\"language-plaintext\">    dict3:\r\n      1:\r\n        name: New Greg\r\n        color: Bright\r\n      2:\r\n        name: Andy\r\n        color: Green\r\n      3:\r\n        name: Andrew\r\n        color: Baby blue<\/code><\/pre>\n<p><strong>Equals or not equals<\/strong><br \/>\nTasks:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: loop through dict3 selectattr == 1\r\n    ansible.builtin.debug:\r\n      msg: &quot;{{ item }}&quot;\r\n    loop: &quot;{{ dict3 | dict2items | selectattr(&#039;key&#039;,&#039;==&#039;,1) }}&quot;\r\n\r\n  - name: loop through dict3 selectattr != 1\r\n    ansible.builtin.debug:\r\n      msg: &quot;{{ item }}&quot;\r\n    loop: &quot;{{ dict3 | dict2items | selectattr(&#039;key&#039;,&#039;!=&#039;,1) }}&quot;<\/code><\/pre>\n<p>Notice here I&#8217;m specifying that they key field either matches 1 in the first task or doesn&#8217;t match 1 in the second task.<\/p>\n<p>Results:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [loop through dict3 selectattr == 1] **************************************\r\nok: [localhost] =&gt; (item={&#039;key&#039;: 1, &#039;value&#039;: {&#039;name&#039;: &#039;New Greg&#039;, &#039;color&#039;: &#039;Bright&#039;}}) =&gt; {\r\n    &quot;msg&quot;: {\r\n        &quot;key&quot;: 1,\r\n        &quot;value&quot;: {\r\n            &quot;color&quot;: &quot;Bright&quot;,\r\n            &quot;name&quot;: &quot;New Greg&quot;\r\n        }\r\n    }\r\n}\r\n\r\nTASK [loop through dict3 selectattr != 1] **************************************\r\nok: [localhost] =&gt; (item={&#039;key&#039;: 2, &#039;value&#039;: {&#039;name&#039;: &#039;Andy&#039;, &#039;color&#039;: &#039;Green&#039;}}) =&gt; {\r\n    &quot;msg&quot;: {\r\n        &quot;key&quot;: 2,\r\n        &quot;value&quot;: {\r\n            &quot;color&quot;: &quot;Green&quot;,\r\n            &quot;name&quot;: &quot;Andy&quot;\r\n        }\r\n    }\r\n}\r\nok: [localhost] =&gt; (item={&#039;key&#039;: 3, &#039;value&#039;: {&#039;name&#039;: &#039;Andrew&#039;, &#039;color&#039;: &#039;Baby blue&#039;}}) =&gt; {\r\n    &quot;msg&quot;: {\r\n        &quot;key&quot;: 3,\r\n        &quot;value&quot;: {\r\n            &quot;color&quot;: &quot;Baby blue&quot;,\r\n            &quot;name&quot;: &quot;Andrew&quot;\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p><strong>Regex searching<\/strong><br \/>\nTask:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: loop through dict3 selectattr search for A in value.name\r\n    ansible.builtin.debug:\r\n      msg: &quot;{{ item }}&quot;\r\n    loop: &quot;{{ dict3 | dict2items | selectattr(&#039;value.name&#039;,&#039;search&#039;,&#039;^A.*&#039;) }}&quot;<\/code><\/pre>\n<p>This one is pretty darn handy.  It allows you to do a regex search on any field, so it will narrow down what results are returned based on what you match.<\/p>\n<p>Results:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [loop through dict3 selectattr search for A in value.name] ****************\r\nok: [localhost] =&gt; (item={&#039;key&#039;: 2, &#039;value&#039;: {&#039;name&#039;: &#039;Andy&#039;, &#039;color&#039;: &#039;Green&#039;}}) =&gt; {\r\n    &quot;msg&quot;: {\r\n        &quot;key&quot;: 2,\r\n        &quot;value&quot;: {\r\n            &quot;color&quot;: &quot;Green&quot;,\r\n            &quot;name&quot;: &quot;Andy&quot;\r\n        }\r\n    }\r\n}\r\nok: [localhost] =&gt; (item={&#039;key&#039;: 3, &#039;value&#039;: {&#039;name&#039;: &#039;Andrew&#039;, &#039;color&#039;: &#039;Baby blue&#039;}}) =&gt; {\r\n    &quot;msg&quot;: {\r\n        &quot;key&quot;: 3,\r\n        &quot;value&quot;: {\r\n            &quot;color&quot;: &quot;Baby blue&quot;,\r\n            &quot;name&quot;: &quot;Andrew&quot;\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p><strong>Compound tests<\/strong><br \/>\nYes, as with any filter plugin you can stack them to check for multiple conditions.  I&#8217;ll take the example from before and build on it.<\/p>\n<p>Task:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: loop through dict3 selectattr search for A in value.name and like green\r\n    ansible.builtin.debug:\r\n      msg: &quot;{{ item }}&quot;\r\n    loop: &quot;{{ dict3 | dict2items | selectattr(&#039;value.name&#039;,&#039;search&#039;,&#039;^A.*&#039;) | selectattr(&#039;value.color&#039;,&#039;eq&#039;,&#039;Green&#039;) }}&quot;<\/code><\/pre>\n<p>It is as simple as adding another pipe and then the next test.  Notice too that I checked different variables in each test.<\/p>\n<p>Results:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [loop through dict3 selectattr search for A in value.name and like green] ***\r\nok: [localhost] =&gt; (item={&#039;key&#039;: 2, &#039;value&#039;: {&#039;name&#039;: &#039;Andy&#039;, &#039;color&#039;: &#039;Green&#039;}}) =&gt; {\r\n    &quot;msg&quot;: {\r\n        &quot;key&quot;: 2,\r\n        &quot;value&quot;: {\r\n            &quot;color&quot;: &quot;Green&quot;,\r\n            &quot;name&quot;: &quot;Andy&quot;\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p><strong>Make sure the variable is defined<\/strong><br \/>\nThe selectattr plugin will fail if one of the entries happens to not have the field to be checked not exist, so I can first check defined, then build on that as follows.<br \/>\nTask:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: loop through dict3 selectattr search for A in value.name and check that it is defined\r\n    ansible.builtin.debug:\r\n      msg: &quot;{{ item }}&quot;\r\n    loop: &quot;{{ dict3 | dict2items | selectattr(&#039;value.name&#039;,&#039;defined&#039;) | selectattr(&#039;value.name&#039;,&#039;search&#039;,&#039;^A.*&#039;) }}&quot;<\/code><\/pre>\n<p>So in this example, it will first check that value.name exists for this host, and if it does, then it will do a regex search on it.  You don&#8217;t necessarily need to do this for every variable you plan to check, but if there&#8217;s a chance it may not be instantiated, then add it; it won&#8217;t hurt to have it and not need it.<\/p>\n<p><strong>Use jinja2 loop to build a variable<\/strong><br \/>\nHere I&#8217;m breaking into a jinja2 loop in conjunction with selectattr to build a custom variable:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: create variable in a jinja2 loop using selectattr\r\n    ansible.builtin.set_fact:\r\n      new_var: &quot;{% for result in dict3 | dict2items | selectattr(&#039;value.name&#039;,&#039;defined&#039;) | selectattr(&#039;value.name&#039;,&#039;search&#039;,&#039;^A.*&#039;) %}\\\r\n                {{ result.key }}: {{ result.value.name }}_{{ result.value.color }},\r\n                {% endfor %}&quot;\r\n\r\n  - name: print out new_var\r\n    ansible.builtin.debug:\r\n      var: new_var.split(&#039;,&#039;)<\/code><\/pre>\n<p>Notice that in the debug statement I&#8217;m using the split option to break the string into a list separated via a comma.<\/p>\n<p>Results:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [create variable in a jinja2 loop using selectattr] ***********************\r\nok: [localhost]\r\n\r\nTASK [print out new_var] *******************************************************\r\nok: [localhost] =&gt; {\r\n    &quot;new_var.split(&#039;,&#039;)&quot;: [\r\n        &quot;2: Andy_Green&quot;,\r\n        &quot; 3: Andrew_Baby blue&quot;,\r\n        &quot; &quot;\r\n    ]\r\n}<\/code><\/pre>\n<h2>Advanced Filtering with map<\/h2>\n<p>Additional resources on map <a href=\"https:\/\/www.middlewareinventory.com\/blog\/ansible-map\/\">here<\/a> and <a href=\"https:\/\/oznetnerd.com\/2017\/04\/18\/jinja2-map-filter\/\">here<\/a>.<br \/>\nThe map plugin allows me to take a list of dictionaries and make a simpler list from it.  Let&#8217;s jump into some examples.<br \/>\nTasks:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: loop through dict3 using map to create a list of names\r\n    ansible.builtin.debug:\r\n      msg: &quot;{{ item }}&quot;\r\n    loop: &quot;{{ dict3 | dict2items | map(attribute=&#039;value.name&#039;) }}&quot;<\/code><\/pre>\n<p>Results:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [loop through dict3 using map to create a list of names] ******************\r\nok: [localhost] =&gt; (item=New Greg) =&gt; {\r\n    &quot;msg&quot;: &quot;New Greg&quot;\r\n}\r\nok: [localhost] =&gt; (item=Andy) =&gt; {\r\n    &quot;msg&quot;: &quot;Andy&quot;\r\n}\r\nok: [localhost] =&gt; (item=Andrew) =&gt; {\r\n    &quot;msg&quot;: &quot;Andrew&quot;\r\n}<\/code><\/pre>\n<p>So as you can see it simply returns a list of values, not key and value.<\/p>\n<p>Say, for example, I wanted to return it all on a single line instead of a list I can do something like the following:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: loop through dict3 using map to create a list of names all on one line\r\n    ansible.builtin.debug:\r\n      msg: &quot;{{ dict3 | dict2items | map(attribute=&#039;value.name&#039;) | join(&#039;,&#039;) }}&quot;<\/code><\/pre>\n<p>I&#8217;m simply using the join plugin and instructing it to use a comma as the joining character.<\/p>\n<p>Results:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">TASK [loop through dict3 using map to create a list of names all on one line] ***\r\nok: [localhost] =&gt; {\r\n    &quot;msg&quot;: &quot;New Greg,Andy,Andrew&quot;\r\n}<\/code><\/pre>\n<h2>Conclusion<\/h2>\n<p>This is a living document, so I plan to make updates periodically.  I hope this gives you a starting point to build some awesome automations.  If you have any questions or comments, please let me know.<\/p>\n<p>Thanks and happy automated looping!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the real strengths of Ansible is it&#8217;s ability to loop through information. This can be done to generate reports or configuring network\u2026<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49],"tags":[],"class_list":["post-7380","post","type-post","status-publish","format-standard","hentry","category-ansible"],"_links":{"self":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7380","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7380"}],"version-history":[{"count":32,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7380\/revisions"}],"predecessor-version":[{"id":7413,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7380\/revisions\/7413"}],"wp:attachment":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7380"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7380"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7380"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}