Converting feature flags into groups in Ansible

George Shuklin
OpsOps
Published in
2 min readFeb 6, 2020

There is a specific case when you want to execute a play for hosts with a specific attribute. For sake of illustration I’ll use hardware_raid: true in all examples, but it should be equally useful for all other cases.

Why use variable? There are groups for that!

The proper way to define which roles are executed on hosts is to use groups. There is one problem here: groups are defined in inventory and you can’t change this in any other place.

In some cases there are different actors producing different parts of resulting set of playbooks, group vars and inventory. Often inventory is generated by some external source (f.e. managing software). In this situation authors of a playbook either must expose the managing software to all details of all group membership (think of raids, f.e.), or find another way to adjust group membership for a given inventory.

This is very different from variables. Maintainers of the playbook can easily amend inventory with group_vars/, set_facts, include_vars, etc. This creates a strong incentive to use variables.

Use 'when’?

The use of variables leads to another question, how to control execution based on variables. The most obvious way is to add ‘when’ to tasks in a role. Let’s say we have role ‘hwraid_mgm_software’. It can add ‘when’ to each task (or use block with when) to control if it need to do something or not.

- name: Example of task with condition
package: name=raid_management_software state=installed
when: hardware_raid

This sounds fine if there a just a few tasks to perform. If a role have many tasks, it causes a lot of ‘skips’ for tasks. This is slow, verbose and hard to debug (given the fact that ‘register’ changes variables even for skipped tasks, it’s really hard to cover all cases in tests).

So, the better solution is needed.

Introducing group_by

Ansible has group_by module. All examples in documentation cares about using different groups based on the value of variable. Moreover, all examples in documentation are focused on values of those variables, not their existence.

Actually, both of those are not necessary.

Here is the example (the whole playbook) for working with feature flags:

---
- hosts: all
tasks:
- group_by:
key: hardware_raid
parents:
- raids
when: hardware_raid|d(False)
- debug: var=[groups.hardware_raids,groups.raids]
run_once: true
- hosts: hardware_raids
tasks:
- debug: msg="I'm hardware raid!"
- hosts: raids
tasks:
- debug: msg="I'm raid!"

As you can see we do ’group_by’ for each host on conditional basis. It allows us to handle both cases ‘not defined’ and ‘set to false’ in the same way. (use of ‘false’ is really useful if you want to have some feature enabled for all except of few hosts).

Important bits

The ‘group_by’ should be a separate play. You need it to be this way because you want your next play to be assigned on that group. (But you can put all ‘group_by’ into a single play if you want).

Downsides

A single downside of that approach I found so far is that --list-hosts option is no longer works for plays with groups filled with ‘group_by’.

Update

Alexey Miasoedov pointed to https://docs.ansible.com/ansible/latest/plugins/inventory/constructed.html plugin. I hadn’t knew about it, and it definitively worth looking into.

--

--

George Shuklin
OpsOps

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