# Loopless alternative?

**URL:** <https://forum.ansible.com/t/loopless-alternative/38341>\
**Category:** Ansible Project\
**Created:** [August 16, 2023, 2:52pm UTC](https://forum.ansible.com/t/loopless-alternative/38341 "2023-08-16T14:52:34Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![dnmvisser](https://avatars.discourse-cdn.com/v4/letter/d/8e8cbc/32.png) [@dnmvisser](https://forum.ansible.com/u/dnmvisser)\
**Post date:** [August 16, 2023, 2:52pm UTC](https://forum.ansible.com/t/loopless-alternative/38341/1 "2023-08-16T14:52:34Z")

</div>

Hii

I have a list of dicts, where I want to inject a number of helper keys.  
I can do this with set\_facts in a loop. Example playbook (hopefully this displays OK):

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [August 16, 2023, 7:17pm UTC](https://forum.ansible.com/t/loopless-alternative/38341/2 "2023-08-16T19:17:25Z")

</div>

Here’s the best I’ve got to offer. I tried pipelining filters etc but couldn’t quite get over the hump. So, back to old school loops.

```auto
    - set_fact:
        backup_objects: |
          {% set result = [] %}
          {% for obj in all_objects %}
          {% set _ = result.append({'basename': obj['Key'] | regex_replace('^' ~ prefix ~ '\d{10}_(.*)\.pgdump', '\1')} | combine(obj)) %}
          {% endfor %}{{ result }}

```

The point of the “set \_” line is for the side effect of appending revised objects to the “result” list. Otherwise it’s pretty straightforward.  
Cheers,

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [August 16, 2023, 10:46pm UTC](https://forum.ansible.com/t/loopless-alternative/38341/3 "2023-08-16T22:46:08Z")

</div>

Create the list of the hashes

&nbsp;&nbsp;bn\_regex: '^{{ prefix }}\d{10}\_(.\*)\.pgdump$'  
&nbsp;&nbsp;bn: "{{ all\_objects|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map(attribute='Key')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('regex\_replace', bn\_regex, '\\1')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('community.general.dict\_kv', 'basename') }}"

gives

&nbsp;&nbsp;bn:  
&nbsp;&nbsp;- basename: dev\_wss\_db  
&nbsp;&nbsp;- basename: dev\_wss\_db\_requests  
&nbsp;&nbsp;- basename: dev\_bss\_service\_database  
&nbsp;&nbsp;- basename: dev\_bss\_frontend\_db  
&nbsp;&nbsp;- basename: dev\_mss\_db

zip the lists and combine the items

&nbsp;&nbsp;backup\_object: "{{ all\_objects|zip(bn)|map('combine') }}"

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [August 16, 2023, 11:34pm UTC](https://forum.ansible.com/t/loopless-alternative/38341/4 "2023-08-16T23:34:26Z")

</div>

There are options. Filter \*basename\* and get rid of \*prefix\*

&nbsp;&nbsp;bn\_regex: '^\d{10}\_(.\*)\.pgdump$'  
&nbsp;&nbsp;bn: "{{ all\_objects|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map(attribute='Key')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('basename')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('regex\_replace', bn\_regex, '\\1')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('community.general.dict\_kv', 'basename') }}"

Create the hash in \*regex\_replace\* and use \*from\_yaml\* instead  
of \*community.general.dict\_kv\*

&nbsp;&nbsp;bn: "{{ all\_objects|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map(attribute='Key')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('basename')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('regex\_replace', bn\_regex, '{basename: \\1}')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('from\_yaml') }}"

Instead of \*regex\_replace\* use \*splitext\* and \*split\*

&nbsp;&nbsp;bn: "{{ all\_objects|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map(attribute='Key')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('basename')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('splitext')|map('first')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('split', '\_', 1)|map('last')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('community.general.dict\_kv', 'basename') }}"

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [August 17, 2023, 11:56am UTC](https://forum.ansible.com/t/loopless-alternative/38341/5 "2023-08-17T11:56:58Z")

</div>

Thanks, Vladimir. I had missed the point of “community.general.dict\_kv”. I had gotten as far as this:

```auto
    - set_fact:
        bn: |
         {{ query('ansible.builtin.nested', ['basename'], (all_objects
            > map(attribute='Key')
            > map('regex_replace', '^' ~ prefix ~ '\d{10}_(.*)\.pgdump', ''))) }}

```

which produces:

```auto
  bn:
  - - basename
    - dev_wss_db
  - - basename
    - dev_wss_db_requests
  - - basename
    - dev_bss_service_database
  - - basename
    - dev_bss_frontend_db
  - - basename
    - dev_mss_db

```

But I didn’t find a way to map that using “community.general.dict” to create

```auto
  bn:
  - basename: dev_wss_db
  - basename: dev_wss_db_requests
  - basename: dev_bss_service_database
  - basename: dev_bss_frontend_db
  - basename: dev_mss_db

```

This for me is one of the more frustrating things about Jinja pipelines. I keep wishing “map” would take arbitrary expressions rather than the limited set it’s stuck with. So you end up with a fleet of one-off filters like “community.general.dict\_kv” which does what “community.general.dict” would do if there were an obvious way to turn this:

```auto
  - - basename
    - dev_wss_db
  - - basename
    - dev_wss_db_requests
  - - basename
    - dev_bss_service_database
  - - basename
    - dev_bss_frontend_db
  - - basename
    - dev_mss_db

```

into this:

```auto
  - - - basename
      - dev_wss_db
  - - - basename
      - dev_wss_db_requests
  - - - basename
      - dev_bss_service_database
  - - - basename
      - dev_bss_frontend_db
  - - - basename
      - dev_mss_db

```

i.e. a “deepen” counterpart to “flatten”. But that magical incantation has so far eluded me.

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [August 17, 2023, 3:07pm UTC](https://forum.ansible.com/t/loopless-alternative/38341/6 "2023-08-17T15:07:40Z")

</div>

> &nbsp;&nbsp;&nbsp;bn:  
> &nbsp;&nbsp;&nbsp;- - basename  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- dev\_wss\_db  
> &nbsp;&nbsp;&nbsp;- - basename  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- dev\_wss\_db\_requests  
> &nbsp;&nbsp;&nbsp;- - basename  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- dev\_bss\_service\_database  
> &nbsp;&nbsp;&nbsp;- - basename  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- dev\_bss\_frontend\_db  
> &nbsp;&nbsp;&nbsp;- - basename  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- dev\_mss\_db
> 
> But I didn't find a way to map that using "community.general.dict" to create
> 
> &nbsp;&nbsp;&nbsp;bn:  
> &nbsp;&nbsp;&nbsp;- basename: dev\_wss\_db  
> &nbsp;&nbsp;&nbsp;- basename: dev\_wss\_db\_requests  
> &nbsp;&nbsp;&nbsp;- basename: dev\_bss\_service\_database  
> &nbsp;&nbsp;&nbsp;- basename: dev\_bss\_frontend\_db  
> &nbsp;&nbsp;&nbsp;- basename: dev\_mss\_db

You can always use brute-force Jinja as the last resort. For example,  
given the list

&nbsp;&nbsp;bn\_list:  
&nbsp;&nbsp;- dev\_wss\_db  
&nbsp;&nbsp;- dev\_wss\_db\_requests  
&nbsp;&nbsp;- dev\_bss\_service\_database  
&nbsp;&nbsp;- dev\_bss\_frontend\_db  
&nbsp;&nbsp;- dev\_mss\_db

the below Jinja creates the list of the hashes

&nbsp;&nbsp;bn: |  
&nbsp;&nbsp;&nbsp;&nbsp;{% filter from\_yaml %}  
&nbsp;&nbsp;&nbsp;&nbsp;{% for basename in bn\_list %}  
&nbsp;&nbsp;&nbsp;&nbsp;- basename: {{ basename }}  
&nbsp;&nbsp;&nbsp;&nbsp;{% endfor %}  
&nbsp;&nbsp;&nbsp;&nbsp;{% endfilter %}

As a side-note, this is equivalent to

&nbsp;&nbsp;bn: "{{ all\_objects|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map(attribute='Key')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('regex\_replace', bn\_regex, '{basename: \\1}')|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map('from\_yaml') }}"

> This for me is one of the more frustrating things about Jinja  
> pipelines. I keep wishing "map" would take arbitrary  
> expressions rather than the limited set it's stuck with.

This is very good point. It would be possible to write such a filter.  
However, I'm not sure about the security implications.

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/ansible/original/1X/1882d9a01da5d8996a862359b0913cc568a39b84.svg) [@system](https://forum.ansible.com/u/system)\
**Post date:** [August 17, 2023, 5:11pm UTC](https://forum.ansible.com/t/loopless-alternative/38341/7 "2023-08-17T17:11:25Z")

</div>

see map/select/reject filters .. they are actually loops and normally  
much simpler than using jinja command syntax ( {% %} ).

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [August 17, 2023, 6:29pm UTC](https://forum.ansible.com/t/loopless-alternative/38341/8 "2023-08-17T18:29:44Z")

</div>

Unfortunately, some filters are not \*map\* friendly. For example, the  
filter \*product\*

&nbsp;&nbsp;list1|product(list2) .............. works fine  
&nbsp;&nbsp;list1|zip(list2)|map('product') ... does not work

Details: Given the list

&nbsp;&nbsp;l1:  
&nbsp;&nbsp;&nbsp;&nbsp;- dir: /tmp/test/d1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sub\_dir: [a, b]  
&nbsp;&nbsp;&nbsp;&nbsp;- dir: /tmp/test/d2  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sub\_dir: [a, b, c]

the goal is to create the list of products

&nbsp;&nbsp;l2:  
&nbsp;&nbsp;- /tmp/test/d1/a  
&nbsp;&nbsp;- /tmp/test/d1/b  
&nbsp;&nbsp;- /tmp/test/d2/a  
&nbsp;&nbsp;- /tmp/test/d2/b  
&nbsp;&nbsp;- /tmp/test/d2/c

The iteration (the filter \*subelements\* not used  
to demonstrate the functionality of \*product\*)

&nbsp;&nbsp;&nbsp;&nbsp;- debug:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg: "{{ [item.0]|product(item.1) }}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loop: "{{ dirs|zip(sdirs) }}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vars:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dirs: "{{ l1|map(attribute='dir') }}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sdirs: "{{ l1|map(attribute='sub\_dir') }}"

works as expected. Gives (abridged)

&nbsp;&nbsp;msg:  
&nbsp;&nbsp;- - /tmp/test/d1  
&nbsp;&nbsp;&nbsp;&nbsp;- a  
&nbsp;&nbsp;- - /tmp/test/d1  
&nbsp;&nbsp;&nbsp;&nbsp;- b

&nbsp;&nbsp;msg:  
&nbsp;&nbsp;- - /tmp/test/d2  
&nbsp;&nbsp;&nbsp;&nbsp;- a  
&nbsp;&nbsp;- - /tmp/test/d2  
&nbsp;&nbsp;&nbsp;&nbsp;- b  
&nbsp;&nbsp;- - /tmp/test/d2  
&nbsp;&nbsp;&nbsp;&nbsp;- c

But, the filter \*product\* doesn't work with \*map\*

&nbsp;&nbsp;dirs: "{{ l1|map(attribute='dir') }}"  
&nbsp;&nbsp;sdirs: "{{ l1|map(attribute='sub\_dir') }}"  
&nbsp;&nbsp;l3: "{{ dirs|zip(sdirs)|map('product') }}"

gives

&nbsp;&nbsp;l3:  
&nbsp;&nbsp;- - - /tmp/test/d1  
&nbsp;&nbsp;&nbsp;&nbsp;- - - a  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- b  
&nbsp;&nbsp;- - - /tmp/test/d2  
&nbsp;&nbsp;&nbsp;&nbsp;- - - a  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- b  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- c

This leaves you with Jinja if you want to avoid the loops in tasks

&nbsp;&nbsp;l3: |  
&nbsp;&nbsp;&nbsp;&nbsp;{% filter from\_yaml %}  
&nbsp;&nbsp;&nbsp;&nbsp;{% for i in l1 %}  
&nbsp;&nbsp;&nbsp;&nbsp;{% for s in i.sub\_dir %}  
&nbsp;&nbsp;&nbsp;&nbsp;- {{ i.dir }}/{{ s }}  
&nbsp;&nbsp;&nbsp;&nbsp;{% endfor %}  
&nbsp;&nbsp;&nbsp;&nbsp;{% endfor %}  
&nbsp;&nbsp;&nbsp;&nbsp;{% endfilter %}

---

<div class="post-metadata">

**Author:** ![dnmvisser](https://avatars.discourse-cdn.com/v4/letter/d/8e8cbc/32.png) [@dnmvisser](https://forum.ansible.com/u/dnmvisser)\
**Post date:** [August 18, 2023, 1:34pm UTC](https://forum.ansible.com/t/loopless-alternative/38341/9 "2023-08-18T13:34:49Z")

</div>

I ended up with the inline jinja for loop.  
Also I used a pipe lookup instead of an s3 list task prior to this one.

So now everything is done in a one single task, without noise, and with the same results.  
Thanks everyone, it was really useful and I will keep it in mind for future reference.

Dick
