<p>I found a solution using file globs.  Since I have a configuration file for each site, I can simply use the list of those files to iterate over all of them.   That way I don't have the list of sites in my task file even once, let alone twice.   All I need to do to add a site is add a file.</p>

<p>To make things a little easier I created a directory for the templates:</p>

<ul>
<li><code>roles/webserver/templates/apache-sites/sitea.conf.j2</code></li>
<li><code>roles/webserver/templates/apache-sites/siteb.conf.j2</code></li>
<li><code>roles/webserver/templates/apache-sites/sitec.conf.j2</code></li>
<li><code>roles/webserver/templates/apache-sites/sited.conf.j2</code></li>
</ul>

<p>Then in <code>roles/webserver/tasks/main.yml</code> I can use that list of files and some regular expressions:</p>

<pre><code>---
- block:
  - name: Install apache site conf
    template: src={{item}} dest=/etc/apache2/sites-available/{{item|regex_replace(".*/","")|regex_replace("\.j2$","")}} mode=0644
    with_fileglob:
    - "roles/webserver/templates/apache-sites/*"
  - name: Enable site apache conf
    command: a2ensite {{item|regex_replace(".*/","")|regex_replace("\.conf\.j2$","")}}
    args:
      creates: /etc/apache2/sites-enabled/{{item|regex_replace(".*/","")|regex_replace("\.j2$","")}}
    with_fileglob:
    - "roles/webserver/templates/apache-sites/*"
  become: yes
</code></pre>

<p>This technique could even be used with empty dummy files to create a list for other applications.</p>

CC BY-SA 3.0
2018-01-08T16:05:49.950Z
2018-01-08T16:24:58.400Z
2018-01-08T16:24:58.400Z
Stephen Ostermiller
<p>Konstantin gave a good answer; here is an additional flavor on it.</p>

<p>I will commonly define the lists as variables, and just write two separate loops over the same variable:</p>

<pre><code>┌─[jamesph@geror] - [~/temp] - [Sat Jan 13, 10:06]
└─[$]&gt; cat loops-1.yml
- hosts: localhost
  gather_facts: no
  vars:
    menu:
      - Egg and Spam
      - Spam, bacon, sausage and Spam
      - Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam
  tasks:
    - debug:
        msg: "We have {{ item }}"
      with_items: "{{ menu }}"
    - debug:
        msg: "I love {{ item }}!"
      with_items: "{{ menu }}"
┌─[jamesph@geror] - [~/temp] - [Sat Jan 13, 10:06]
└─[$]&gt; ansible-playbook loops-1.yml
 [WARNING]: Unable to parse /etc/ansible/hosts as an inventory source

 [WARNING]: No inventory was parsed, only implicit localhost is available

 [WARNING]: Could not match supplied host pattern, ignoring: all

 [WARNING]: provided hosts list is empty, only localhost is available


PLAY [localhost] **********************************************************************************************************************

TASK [debug] **************************************************************************************************************************
ok: [localhost] =&gt; (item=Egg and Spam) =&gt; {
    "changed": false,
    "item": "Egg and Spam",
    "msg": "We have Egg and Spam"
}
ok: [localhost] =&gt; (item=Spam, bacon, sausage and Spam) =&gt; {
    "changed": false,
    "item": "Spam, bacon, sausage and Spam",
    "msg": "We have Spam, bacon, sausage and Spam"
}
ok: [localhost] =&gt; (item=Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam) =&gt; {
    "changed": false,
    "item": "Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam",
    "msg": "We have Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam"
}

TASK [debug] **************************************************************************************************************************
ok: [localhost] =&gt; (item=Egg and Spam) =&gt; {
    "changed": false,
    "item": "Egg and Spam",
    "msg": "I love Egg and Spam!"
}
ok: [localhost] =&gt; (item=Spam, bacon, sausage and Spam) =&gt; {
    "changed": false,
    "item": "Spam, bacon, sausage and Spam",
    "msg": "I love Spam, bacon, sausage and Spam!"
}
ok: [localhost] =&gt; (item=Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam) =&gt; {
    "changed": false,
    "item": "Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam",
    "msg": "I love Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam!"
}

PLAY RECAP ****************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
</code></pre>

<p>This works nicely with <a href="https://docs.ansible.com/ansible/latest/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable" rel="nofollow noreferrer">variable precedence</a>, for instance by defining different sets of servers per environment.  It also works when you need to perform various other non-looped tasks in-between the two loops.</p>

CC BY-SA 3.0
2018-01-13T18:11:57.770Z
2018-01-13T18:11:57.770Z
Xiong Chiamiov
<p>Make separate tasks file <code>make_site.yml</code>:</p>

<pre><code>---
- name: Install apache site conf
  template:
    src: apache-sites-{{ site }}-conf.j2
    dest: /etc/apache2/sites-available/{{ site }}.conf
    mode: 0644

- name: Enable site apache conf
  command: a2ensite {{ site }}
  args:
    creates: /etc/apache2/sites-enabled/{{ site }}.conf
</code></pre>

<p>And in your playbook:</p>

<pre><code>- include_tasks: make_site.yml
  with_items:
    - sitea
    - siteb
    - sitec
    - sited
  loop_control:
    loop_var: site
</code></pre>

CC BY-SA 4.0
2019-03-13T13:27:08.290Z
I was hoping to find a solution without having to create a separate file. Something like a `loop` on a `block` block. That seems to be requested, but rejected via [Ansible GH issue 13262](https://github.com/ansible/ansible/issues/13262). `:-(`
gertvdijk
CC BY-SA 3.0
2017-12-28T16:59:21.597Z
2017-12-28T16:59:21.597Z
Konstantin Suvorov
<p>I am using an ansible playbook to configure Apache for a list of sites.   The playbook has to copy the virtual host configuration template for each site into place, and then enable each site using <code>a2ensite</code>:</p>

<pre><code>- name: Install apache site conf
  template: src=apache-sites-{{item}}-conf.j2 dest=/etc/apache2/sites-available/{{item}}.conf mode=0644
  with_items:
  - sitea
  - siteb
  - sitec
  - sited
- name: Enable site apache conf
  command: a2ensite {{item}}
  args:
    creates: /etc/apache2/sites-enabled/{{item}}.conf
  with_items:
  - sitea
  - siteb
  - sitec
  - sited
</code></pre>

<p>I don't like having to repeat the same list for each task.   How to I configure the playbook to execute both tasks with the same list of items?</p>

<ansible>
Execute multiple Ansible tasks with the same list of items
Stephen Ostermiller
# Execute multiple Ansible tasks with the same list of items

(Asked by: username_0 on 2017-12-28)

I am using an ansible playbook to configure Apache for a list of sites.   The playbook has to copy the virtual host configuration template for each site into place, and then enable each site using a2ensite:
- name: Install apache site conf
  template: src=apache-sites-{{item}}-conf.j2 dest=/etc/apache2/sites-available/{{item}}.conf mode=0644
  with_items:
  - sitea
  - siteb
  - sitec
  - sited
- name: Enable site apache conf
  command: a2ensite {{item}}
  args:
    creates: /etc/apache2/sites-enabled/{{item}}.conf
  with_items:
  - sitea
  - siteb
  - sitec
  - sited

I don't like having to repeat the same list for each task.   How to I configure the playbook to execute both tasks with the same list of items?

**Tags:** ansible


---

## 3 answers

### Answer by username_0 on 2018-01-08. Score: 2 | Accepted answer

I found a solution using file globs.  Since I have a configuration file for each site, I can simply use the list of those files to iterate over all of them.   That way I don't have the list of sites in my task file even once, let alone twice.   All I need to do to add a site is add a file.
To make things a little easier I created a directory for the templates:

roles/webserver/templates/apache-sites/sitea.conf.j2
roles/webserver/templates/apache-sites/siteb.conf.j2
roles/webserver/templates/apache-sites/sitec.conf.j2
roles/webserver/templates/apache-sites/sited.conf.j2

Then in roles/webserver/tasks/main.yml I can use that list of files and some regular expressions:
---
- block:
  - name: Install apache site conf
    template: src={{item}} dest=/etc/apache2/sites-available/{{item|regex_replace(".*/","")|regex_replace("\.j2$","")}} mode=0644
    with_fileglob:
    - "roles/webserver/templates/apache-sites/*"
  - name: Enable site apache conf
    command: a2ensite {{item|regex_replace(".*/","")|regex_replace("\.conf\.j2$","")}}
    args:
      creates: /etc/apache2/sites-enabled/{{item|regex_replace(".*/","")|regex_replace("\.j2$","")}}
    with_fileglob:
    - "roles/webserver/templates/apache-sites/*"
  become: yes

This technique could even be used with empty dummy files to create a list for other applications.


---

### Answer by username_2 on 2017-12-28. Score: 21

Make separate tasks file make_site.yml:
---
- name: Install apache site conf
  template:
    src: apache-sites-{{ site }}-conf.j2
    dest: /etc/apache2/sites-available/{{ site }}.conf
    mode: 0644

- name: Enable site apache conf
  command: a2ensite {{ site }}
  args:
    creates: /etc/apache2/sites-enabled/{{ site }}.conf

And in your playbook:
- include_tasks: make_site.yml
  with_items:
    - sitea
    - siteb
    - sitec
    - sited
  loop_control:
    loop_var: site

> **Comments:**
> **username_3**: I was hoping to find a solution without having to create a separate file. Something like a `loop` on a `block` block. That seems to be requested, but rejected via [Ansible GH issue 13262](https://github.com/ansible/ansible/issues/13262). `:-(`  

---

### Answer by username_1 on 2018-01-13. Score: 2

Konstantin gave a good answer; here is an additional flavor on it.
I will commonly define the lists as variables, and just write two separate loops over the same variable:
┌─[jamesph@geror] - [~/temp] - [Sat Jan 13, 10:06]
└─[$]> cat loops-1.yml
- hosts: localhost
  gather_facts: no
  vars:
    menu:
      - Egg and Spam
      - Spam, bacon, sausage and Spam
      - Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam
  tasks:
    - debug:
        msg: "We have {{ item }}"
      with_items: "{{ menu }}"
    - debug:
        msg: "I love {{ item }}!"
      with_items: "{{ menu }}"
┌─[jamesph@geror] - [~/temp] - [Sat Jan 13, 10:06]
└─[$]> ansible-playbook loops-1.yml
 [WARNING]: Unable to parse /etc/ansible/hosts as an inventory source

 [WARNING]: No inventory was parsed, only implicit localhost is available

 [WARNING]: Could not match supplied host pattern, ignoring: all

 [WARNING]: provided hosts list is empty, only localhost is available


PLAY [localhost] **********************************************************************************************************************

TASK [debug] **************************************************************************************************************************
ok: [localhost] => (item=Egg and Spam) => {
    "changed": false,
    "item": "Egg and Spam",
    "msg": "We have Egg and Spam"
}
ok: [localhost] => (item=Spam, bacon, sausage and Spam) => {
    "changed": false,
    "item": "Spam, bacon, sausage and Spam",
    "msg": "We have Spam, bacon, sausage and Spam"
}
ok: [localhost] => (item=Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam) => {
    "changed": false,
    "item": "Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam",
    "msg": "We have Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam"
}

TASK [debug] **************************************************************************************************************************
ok: [localhost] => (item=Egg and Spam) => {
    "changed": false,
    "item": "Egg and Spam",
    "msg": "I love Egg and Spam!"
}
ok: [localhost] => (item=Spam, bacon, sausage and Spam) => {
    "changed": false,
    "item": "Spam, bacon, sausage and Spam",
    "msg": "I love Spam, bacon, sausage and Spam!"
}
ok: [localhost] => (item=Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam) => {
    "changed": false,
    "item": "Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam",
    "msg": "I love Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam, Spam and Spam!"
}

PLAY RECAP ****************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0

This works nicely with variable precedence, for instance by defining different sets of servers per environment.  It also works when you need to perform various other non-looped tasks in-between the two loops.


---

