Big Bubbles (no troubles)

What sucks, who sucks and you suck

Ansible: Multiple Values in a Registered Variable

Problem: you want to run a command in a loop within Ansible, registering the result of each run to a single variable, and then process those results depending on a condition. Specifically, you want to register the output of a command run over all the elements in a dictionary but then only process the elements where the command returned a particular result.

This appears difficult because registered variables aren’t obviously designed to work with lists and you want the task to check the results in one list variable while processing elements from another data structure.

The key is to realise that register absolutely will work with a list or dictionary and that the resulting registered variable (which contains a dictionary) will store each element of the original list alongside the task results.

In this specific context, I wanted to set a number of resource controls in /etc/projects on a Solaris 11 client. Ansible doesn’t yet have a module to manipulate these natively, so you need to execute the Solaris projadd/projmod commands. Annoyingly, you can add a new project or modify an existing one, but there is no semantic for “create this project if it doesn’t exist but modify its attributes if it does” (hint to Oracle: add a flag for ‘create project if not present’ to the projmod command). Therefore, first of all we need to determine if the project(s) we want to create already exist. In this case, I have a list of database instances which each have a dedicated project and a number of associated attributes.

The instances are listed in a YAML dictionary keyed by the name, with specific per-instance resources (such as maximum shared memory) stored as values. E.g.:

1
2
3
4
5
db_instances:
  instance1:
    shmmem: 8G
  instance2:
    shmmem: 16G

etc.

Now we iterate over this dictionary to see if a project exists for each instance (naturally, the projects are named after the instances):

1
2
3
4
5
6
- name: check for database instance project
  command: projects -l "db_{{ item.key }}"
  with_dict: "{{ db_instances }}"
  register: proj
  ignore_errors: yes
  always_run: yes

(Note that we need to ignore errors, since we’re expecting that at least some of the projects may not exist.) The proj variable will contain a list of results from the command, one for each instance (strictly, each execution). If we display it using the debug module, we can see that each result also contains a complete copy of the subject element from the original dictionary, including all the values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"proj": {
    "results": [
        {
            "item": {
                "key": "instance1",
                "value": {
                    "shmmem": "8G"
                }
            },
            "rc": 1,
            "stderr": "",
            "stdout": "....",
        }
    ]
}

Now we want to run the projadd command for each instance where the rc value was non-zero (meaning that the project didn’t already exist, as the projects -l command returned an error.

1
2
3
4
- name: set up database instance resources
  command: /usr/sbin/projadd -c "{{ item.item.key }} database" -U "{{ item.item.key }}" -G dbagroup -K "project.cpu-shares=(privileged,20,none)" -K "project.max-shm-memory=(privileged,{{ item.item.value.shmmem }},deny)" -K "project.max-shm-ids=(privileged,4096,deny)" -K "project.max-sem-ids=(privileged,4096,deny)" -K "project.max-msg-ids=(privileged,4096,deny)" "db_{{ item.item.key }}"
  when: "{{ item.rc }} != 0"
  with_items: "{{ proj.results }}"

In this case, that means iterating over the list of results (not the original list of instances; the results reference all the elements of the instance dictionary anyway), so that we can test the rc value for each one. If it’s non-zero, the command is executed. Within the command arguments, we can refer to the instance attributes by utilising the unwieldy but correct names item.item.key (the key of the original item from the instance dictionary that is stored in this item of our list) and item.item.value.valuename (a value associated with that key). (Note that most of the resource limits are hardcoded in this example, but there’s no reason they couldn’t be taken from the instances dictionary with - at the expense of increased verbosity - default values as fallbacks.)

For bonus marks, we can code a second pass to execute projmod for instances where the project already exists, to reset the resource attributes. This may mean resetting them to the same value on each run, which is an unfortunate overhead but no harm done. (As an alternative shortcut, we could execute projadd in the initial registered task and then run projmod for all the results where that failed because the project already existed.)

Note that this technique will also work on lists or any other iterative data structure. (Tested on Ansible 2.1, but ought to work on most late releases.)

Other bubbles

  • This post from the nTh among all blog first tipped me off to the structure of registered variables from loops.