Using the results of an Ansible with_items loop
I was recently writing an Ansible playbook where I needed one task to change what it did based on the results of an earlier task. This would seem to be easy, we can just use register
to create a variable to hold the result of the first task, and then use that variable in a when
condition on the second task. In my case, this was complicated slightly by the fact that the initial task included a with_items
loop, and so I needed to be able to make the second task work with the result of that loop.
To illustrate what I ended up doing, lets imagine the situation that we have a server that we want to deploy some webserver config files onto, but the specific config files that we need to deploy depend on the presence - or absence - of some other files. In our example these will be SSL certificate files. So, say, if foo.com.crt
is present on the server, but bar.com.crt
is not, then we need to deploy foo.com.ssl.conf
and bar.com.no_ssl.conf
.
We should start off by checking which of our cert files are present on the server, using stat
:
1- name: Check if certs exist 2 stat: 3 path: "/etc/path/to/certs/{% raw %}{{ item.host }}{% endraw %}.crt" 4 with_items: 5 - { host: "foo.com", log_file: "foo_com" } 6 - { host: "bar.com", log_file: "bar_com" } 7 register: certs
We now have a variable called certs
containing the results of each of our two stat
checks. It's now really easy to deploy the correct version of our config files using this result. First the case that the cert file did not exist:
1- name: Install nginx config (No SSL) 2 template: 3 src: "nginx_config.no_ssl.conf.j2" 4 dest: "/etc/nginx/conf.d/{% raw %}{{ item.item.host }}{% endraw %}.no_ssl.conf" 5 with_items: "{% raw %}{{ certs.results }}{% endraw %}" 6 when: item.stat.exists == False 7 notify: 8 - "reload nginx"
If /etc/path/to/certs/foo.com.crt
existed, but /etc/path/to/certs/bar.com.crt
did not, this would cause /etc/nginx/conf.d/bar.com.no_ssl.conf
to be created, but would do nothing for the foo.com
case. To install a config for the case where the .crt
file did exist, we just need a similar task, using the opposite test in its when
condition:
1- name: Install nginx config (SSL) 2 template: 3 src: "nginx_config.ssl.conf.j2" 4 dest: "/etc/nginx/conf.d/{% raw %}{{ item.item.host }}{% endraw %}.ssl.conf" 5 with_items: "{% raw %}{{ certs.results }}{% endraw %}" 6 when: item.stat.exists == True 7 notify: 8 - "reload nginx"
This would now install foo.com.ssl.conf
, and do nothing in the bar.com
case.
We can also access the contents of our loop variables (from our original stat
task) inside of the templates we are using in these tasks. We just have to be careful to access this original data via item.item
- not just item
. For example, our nginx_config.no_ssl.conf.j2
template might look like this:
1server { 2 listen 80; 3 server_name {% raw %}{{ item.item.host }}{% endraw %}; 4 5 error_log /var/log/nginx/{% raw %}{{ item.item.log_file }}{% endraw %}_error.log; 6 access_log /var/log/nginx/{% raw %}{{ item.item.log_file }}{% endraw %}_access.log detail; 7 8 location / { 9 try_files $uri $uri/index.html $uri.html =404; 10 }; 11}
And that's all there is to it!
If you want to read further, the official Ansible documentation for using register
with loops can be found here.