I was recently writing an Ansible playbook where I needed one to 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:

- name: Check if certs exist
  stat:
    path: "/etc/path/to/certs/{{ item.host }}.crt"
  with_items:
    - { host: 'foo.com', log_file: 'foo_com' }
    - { host: 'bar.com', log_file: 'bar_com' }
  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:

- name: Install nginx config (No SSL)
  template:
    src: "nginx_config.no_ssl.conf.j2"
    dest: "/etc/nginx/conf.d/{{ item.item.host }}.no_ssl.conf"
  with_items: "{{ certs.results }}"
  when: item.stat.exists == False
  notify:
    - "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:

- name: Install nginx config (SSL)
  template:
    src: "nginx_config.ssl.conf.j2"
    dest: "/etc/nginx/conf.d/{{ item.item.host }}.ssl.conf"
  with_items: "{{ certs.results }}"
  when: item.stat.exists == True
  notify:
    - "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:

server {
    listen 80;
    server_name {{ item.item.host }};
    
    error_log /var/log/nginx/{{ item.item.log_file }}_error.log;
    access_log /var/log/nginx/{{ item.item.log_file }}_access.log detail;

    location / {
        try_files $uri $uri/index.html $uri.html =404;
    };
}

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.