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