How to Check if a Dictionary Key is Undefined in Ansible Tasks


2 views

When working with Ansible, you might encounter situations where you need to check if a nested dictionary key exists before executing a task. The naive approach of using when: me.cool is not defined fails spectacularly when me exists but cool doesn't.

The error message you're seeing:

{"failed": true, "msg": "The conditional check 'me.cool' failed. The error was: error while evaluating conditional (me.cool): 'dict object' has no attribute 'cool'."}

occurs because Ansible tries to access the cool attribute before checking if it exists. This is different from Python's dictionary behavior where you could use .get() with a default value.

Here are several ways to properly check for undefined dictionary keys in Ansible:

1. Using the 'in' Operator

- name: cool task
  shell: 'touch iamnotcool.txt'
  when: "'cool' not in me"

2. Using the Default Filter

- name: cool task
  shell: 'touch iamnotcool.txt'
  when: me.cool | default('') == ''

3. Using the Omitted Filter (Ansible 2.8+)

- name: cool task
  shell: 'touch iamnotcool.txt'
  when: me.cool is omitted

4. Using the Has Key Test

- name: cool task
  shell: 'touch iamnotcool.txt'
  when: not me is defined or not me.has_key('cool')

For deeply nested dictionaries, you can chain these methods:

- name: complex check
  debug:
    msg: "Value exists"
  when: 
    - foo is defined
    - "'bar' in foo"
    - "'baz' in foo.bar"

While all these methods work, the in operator is generally the most performant as it doesn't require additional filter processing. The omitted filter (solution 3) is the most readable but requires Ansible 2.8 or later.

Here's how you might use this in a role to conditionally configure services:

- name: Configure optional feature
  template:
    src: feature.conf.j2
    dest: /etc/feature.conf
  when: "'enable_feature' in app_config"

When working with Ansible dictionaries, you might encounter this common scenario:

- name: cool task
  shell: 'touch iamnotcool.txt'
  when: me.cool is not defined

With variables defined as:

---
me:
  stumped: yes

This fails with the error:

{"failed": true, "msg": "The conditional check 'me.cool' failed. The error was: error while evaluating conditional (me.cool): 'dict object' has no attribute 'cool'."}

Ansible's is not defined check works for top-level variables but not for nested dictionary keys. When you try to check me.cool is not defined, Ansible first tries to access the cool attribute, which throws an error before the is not defined check can be evaluated.

1. Using the 'in' operator

The most straightforward solution is to check if the key exists in the dictionary:

- name: cool task
  shell: 'touch iamnotcool.txt'
  when: 'cool' not in me

2. Using the default filter

You can use Jinja2's default filter to provide a fallback value:

- name: cool task
  shell: 'touch iamnotcool.txt'
  when: me.cool | default('') == ''

3. Combining both approaches

For maximum safety, you can combine these techniques:

- name: cool task
  shell: 'touch iamnotcool.txt'
  when: me is defined and 'cool' not in me

Handling nested dictionaries

For deeply nested dictionaries, you might need recursive checks:

- name: deep check task
  shell: 'echo "deep nesting"'
  when: 
    - me is defined
    - 'level1' in me
    - 'level2' in me.level1
    - 'target_key' not in me.level1.level2

Creating a custom test plugin

For frequent use, consider creating a custom test plugin:

# In library/tests/dict_has_key.py
def dict_has_key(d, key):
    return key in d if isinstance(d, dict) else False

class TestModule(object):
    def tests(self):
        return {
            'has_key': dict_has_key
        }

Usage:

- name: use custom test
  debug:
    msg: "Key exists!"
  when: me is has_key('cool')
  • Not checking if the parent dictionary exists first
  • Assuming is not defined works for dictionary keys
  • Forgetting that default() filters can mask other issues

The in operator is generally the most efficient method for key existence checking. For large dictionaries or complex nested structures, consider:

  • Pre-filtering data before passing to Ansible
  • Using json_query filters for complex structures
  • Caching results if the same check is used multiple times