2.5: delegate_to, include_role with loops
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:
- We can not use loop with import, we need to use include.
- We can not use delegate to with include, we need to use import.
Solution:
- We use loop + include_tasks to run a separate tasklist.
2. We use import_role + delegate_to to delegate a role to another host.