Ansible anti-pattern: import_role task with task-level vars
I want to share with you some of the greatest WTFs in Ansible I encountered in last weeks. As it turns out, it become one of the anti-patterns you should avoid at any cost.
Anti-pattern
Never do anything like that:
- import_role: somerole
vars:
somevar: somevalue
This is anti-pattern to evade at any cost. Tested in Ansible 2.7 and 2.8.
Small WTF
Inventory:
localhost foo=1 ansible_connection=local
roles/role1/tasks/main.yaml:
---
- debug: var=foo
play.yaml:
---
- hosts: localhost
tasks:
- debug: var=foo
- import_role: name=role1
vars:
foo: 2
- debug: var=foo
This should print foo
value three times: before the import, inside an imported role and after the import. What are you expectations for those three values?
Just to remind: inventory have foo=1
, import_role
have local vars where foo=2
.
My naive thinking was: 1, 2, 1.
As you can guess from the title of this article, it wasn’t.
The answer is 2, 2, 2. It’s described here: https://docs.ansible.com/ansible/latest/modules/import_role_module.html:
Since Ansible 2.7 variables defined in
vars
anddefaults
for the role are exposed at playbook parsing time. Due to this, these variables will be accessible to roles and tasks executed before the location of the import_role task.
So, the usual minor WTF of the Ansible. Enough to mark the use of ‘vars’ for import_role
as anti-pattern. But wait, there is more!
Big WTF
The same inventory (foo=1
), the same role with simple debug
.
A slightly more interesting playbook:
- hosts: localhost
tasks:
- debug: var=foo
- import_role: name=role1
vars:
foo: 2
- import_role: name=role1
vars:
foo: 3
- debug: var=foo
Your guess?
It is [3,2,3,3]. This is madness, IMHO.
As you can see, value of foo
is local for [n-1] imports, and is global for playbook for last import.
Why it’s anti-pattern?
It’s misleading, that’s why. Every other task call (including include_role
) treats task-level ‘vars’ as task-level. You expect to see local (task-level) overrides at task-level vars. This is intuition. Yet, those variables affects other tasks, moreover, they affect tasks above import_role
. Any person would misunderstand the intention. When intention and result are differ, it’s the anti-pattern for any language.
Dry conclusion
Assume any use of vars for import_role task to be anti-pattern and avoid it at any cost.
Emotional whining
Ansible uses all possible tricks to make impossible to write a pure function with no affluence on a global state. EVERYTHING YOU DO HAVE A GLOBAL SIDE EFFECT.
You can not reuse code without tainting your variables. It’s not possible to write a code which does not taint global variables. Even if you find a way to do so, Ansible will find you at night and will rip your <s>eyes</s> pure functions off.