Ansible anti-pattern: import_role task with task-level vars

George Shuklin
OpsOps
Published in
2 min readOct 16, 2019

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 and defaults 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.

--

--

George Shuklin
OpsOps

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