Integrating playbook group_vars into testinfra

George Shuklin
OpsOps
Published in
3 min readOct 9, 2021

Short news: There is a way to access playbook level group_vars from testinfra, if playbook_dir is configured in ansible.cfg.

If this too cryptic, here is a long explanation.

Two types of group_vars

It’s not well known, but ansible supports two types of group vars: inventory group vars and playbook group vars. Inventory group_vars is a big and complicated topic (there are group vars inside inventory, there can be multiple inventories at the same time, there are inventory group_vars in a separate directory and they have very complex priority rules).

But! There are another group_vars, which are much simpler. Playbook may have own group_vars (and host_vars, if you want, but I can’t phantom anyone daring to call playbook host vars a reasonable solution).

Because I hate typing group_vars every time, I’ll call them gv from this point.

The key difference between them is that inventories and their gv be replaced, gv for playbook will be the same, because they are linked to playbook.

Why you may want to use playbook level gv? There are moments when you have set of variables, which are absolutely outside of inventory scope, but needed for two or more roles. Let’s say you have an application and nginx on top of it. They need a specific port number to communicate. You need to write it into app.conf.j2 in app role and in nginx.conf in nginx role.

What options you have? First, if they are configured in a single play, you can write like this:

- hosts: app,proxy
roles:
...
vars:
app_port: 32767

And problem solved. But if you have two separate roles, which may run on separate hosts, that won’t work. Two other solutions:

  • hard-code number into both templates. This is ok for super rare changes (like grafana port), generally, having it as a hard-coded number make it hard to debug and update.
  • write them as the ‘same-named’ variable in defaults for each role. This is easier to manage (if you need to move them to another port, there is a variable for this), but it’s very brittle, as you have two independent roles having unofficial agreement on variable name. (actually, hard-coded port number is the same type of agreement).
  • Create a dedicated role with defaults to include in all plays. It’s ugly, having too many moving parts and generally I aganst this.

Finally, we can use playbook level variables. If those two groups (app, proxy) has a common ancestor, it’s a good place for such varaible. If not, unfortunately, spamming ‘all’ is always an option.

The key thing, that those variables are ‘common’ for two plays and they are the single source of truth, which is good.

BTW, if you have inventory in the same directory as playbooks, those (inventory gv and playbook gv) are the same files, so you just don’t think much about them (until precedence rules bite you where it hurts).

Anyway, going to second part of the problem, pytest/testinfra

testinfra and group_vars

testinfra has ability to access hostvars (not host_vars), the magical variable hostvars with collapsed variables from all sources. Generally, it solves the problem if you need to peek into other group vars.

But… there is no play for testinfra. Under the hood it uses ansible-inventory to extract all those parameters. And ansible-inventory is not ansible-playbook, there are no playbooks for inventory. So, testinfra just ignores for inventory-level variables.

Now, to the question: how to access playbook group_vars inside testinfra?

The answer

Turned out, this problem was solved few versions ago: there is a special variable playbook_dir in ansible.cfg, which is used to search for playbook level group_vars to include them into ansible and ansible-inventory runs.

And we can use it! After defining playbook_dir , testinfra starts to see playbook level group_vars, which allows wonderful clean interaction between tests and the code.

--

--

George Shuklin
OpsOps

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