2.5: delegate_to, include_role with loops

George Shuklin
OpsOps
Published in
2 min readApr 17, 2018

There is a horrible regression in Ansible 2.5.

In Ansible version 2.4, if you have combination of delegate_to and include_role a given role was delegated to specified host. In Ansible 2.5 delegated role will be executed on the original host, causing a big mess to debug. Actually, this problem affects on only include_role, but all other dynamic includes (f.e. include_tasks). The problem was escalated by the fact that only ‘include’ allowed to be used together with loop statements (with_items, with_nested, etc).

After some thoughts, I found a way to do delegation and includes at the same time together with loops. The solution in the nutshell: use iteration over included tasklist, use delegation within tasklist for imports.

Example

I’ll start with bad example. It had worked in Ansible 2.4, it is silently broken in Ansible 2.5.

Bad code

role2/tasks/main.yaml:

---
- include_role: name=role1
delegate_to: localhost
with_items:
- one
- two

role1/tasks/main.yaml

- shell: hostname
register: h
- debug: msg="host: {{h}}, var={{item}}"

play.yaml

- hosts: somehost
gather_facts: no
roles:
- role2

Line to execute:

ansible-playbook -i somehost, play.yaml

You need to have ‘somehost’ to be accessible from your machine by ssh. Replace it with any known hostnames (except for localhost). You need to replace it in ‘line to execute’ and in play.yaml in the ‘hosts’ line.

You will find that regardless of using delegation to ‘localhost’, this playbook will output hostname of remote machine. What a bummer.

Good code

This code works in both ansible 2.5 and ansible 2.4. It will output your local machine hostname, as expected.

role2/tasks/main.yaml (changed):

---
- include_tasks: loop.yaml
with_items:
- one
- two

role2/tasks/loop.yaml: (it’s a new file!)

---
- import_role: name=role1
delegate_to: localhost

role1/tasks/main.yaml (the same file as before)

- shell: hostname
register: h
- debug: msg="host: {{h}}, var={{item}}"

play.yaml (the same file as before)

- hosts: somehost
gather_facts: no
roles:
- role2

After executing the same line:

ansible-playbook -i somehost, play.yaml

You will find that hostname output have been changed to a localhost hostname (expected behavior).

The solution, explained

Input:

  1. We can not use loop with import, we need to use include.
  2. We can not use delegate to with include, we need to use import.

Solution:

  1. We use loop + include_tasks to run a separate tasklist.

2. We use import_role + delegate_to to delegate a role to another host.

--

--

George Shuklin
OpsOps

I work at Servers.com, most of my stories are about Ansible, Ceph, Python, Openstack and Linux. My hobby is Rust.