Ansible: Defined is not Truthy
I was creating some ansible playbooks recently, and I came across an interesting issue.
In my variable definition file, this:
vars/main.yml
test_var: # This is a test var
When evaluating whether or not it was defined:
- name: Print test var
ansible.builtin.debug:
var: test_var
- name: Is null defined
ansible.builtin.debug:
msg: "test_var is defined"
when: test_var is defined
This would actually evaluate to true:
TASK [Print test var] ************************************************************************************************************************************
ok: [localhost] => {
"test_var": null
}
TASK [Is null defined] ***********************************************************************************************************************************
ok: [localhost] => {
"msg": "test_var is defined" }
So apparently, null is defined. This is a somewhat weird behavior.
Apparently, it’s only when variables are completly unset, unmentioned in any files, that a variable is not considered defined.
However, even if variables are not defined, then the value of null
is still considerd to be falsey
- name: What about truthyism?
ansible.builtin.debug:
msg: "test_var is truthy"
when: test_var
- name: What about the bool filter
ansible.builtin.debug:
msg: "test var passes the bool"
when: test_var | bool
TASK [What about truthyism?] *****************************************************************************************************************************
skipping: [localhost]
TASK [What about the bool filter] ************************************************************************************************************************ skipping: [localhost]
And these tasks are skipped, because null
is falsey, and doesn’t satisfy the when statements.
What about an empty string?
empty_string: ""
- name: What about a length filter?
ansible.builtin.debug:
msg: "empty_string passes the length filter"
when: empty_string | length > 0
- name: Print empty_string
ansible.builtin.debug:
var: empty_string
- name: Is empty_string defined
ansible.builtin.debug:
msg: "empty_string is defined"
when: empty_string is defined
- name: What about truthyism?
ansible.builtin.debug:
msg: "empty_string is truthy"
when: empty_string
- name: What about the bool filter
ansible.builtin.debug:
msg: "empty_string passes the bool"
when: empty_string | bool
- name: Empty string truthy in assert?
ansible.builtin.assert:
that: empty_string
TASK [What about a length filter?] ***********************************************************************************************************************
skipping: [localhost]
TASK [Print empty_string] ********************************************************************************************************************************
ok: [localhost] => {
"empty_string": ""
}
TASK [Is empty_string defined] ***************************************************************************************************************************
ok: [localhost] => {
"msg": "empty_string is defined"
}
TASK [What about truthyism?] *****************************************************************************************************************************
skipping: [localhost]
TASK [What about the bool filter] ************************************************************************************************************************
skipping: [localhost]
TASK [Empty string truthy in assert?] ********************************************************************************************************************
fatal: [localhost]: FAILED! => {
"assertion": "empty_string",
"changed": false,
"evaluated_to": false,
"msg": "Assertion failed"
} ...ignoring
So an empty_string is considered defined, falsey, and unlike a null
value, it can also be passed through the length filter, to get falsey.
What about completely unset? Not bothering to mention a variable in any files?
- name: Is variable truthy
ansible.builtin.debug:
msg: "unset is truthy?"
when: unset
ignore_errors: true
- name: Uset variable defined?
ansible.builtin.debug:
msg: "unset is defined"
when: unset is defined
TASK [Empty string truthy in assert?] ********************************************************************************************************************
fatal: [localhost]: FAILED! => {
"assertion": "empty_string",
"changed": false,
"evaluated_to": false,
"msg": "Assertion failed"
}
...ignoring
TASK [Is variable truthy] ********************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The conditional check 'unset' failed. The error was: error while evaluating conditional (unset): 'unset' is undefined. 'unset' is undefined\n\nThe error appears to be in '/stuff/playbook.yml': line 53, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n ignore_errors: true\n - name: Is variable truthy\n ^ here\n"}
...ignoring
TASK [Uset variable defined?] **************************************************************************************************************************** skipping: [localhost]
So this is an interesting phenomenon. Trying to check a completely undefined variable for truthyness doesn’t work.
What about an is defined, and a check for truthyness? For my specific usecase, I have an ansible role that generates a variable, that some roles may rely on. If the first role isn’t run or run out of order, things could break.
I want an ansible.builtin.assert
, which essentially checks some conditions, and fails, stopping the playbook if they are not met. How can I check if a variable is defined first, and then truthy? Now, a check for truthyism will still fail, but without the assert catching the error, the error message won’t be as explicit.
I created a variable called notunset
, and set it to “stuff”, and ran some similar tests.
- name: Notunset variable defined?
ansible.builtin.debug:
msg: "unset is defined"
when: notunset is defined
- name: Notunset variable defined and truthy?
ansible.builtin.assert:
that: notunset is defined and notunset | bool
ignore_errors: true
TASK [Is notunset variable truthy] ***********************************************************************************************************************
ok: [localhost] => {
"msg": "notunset is truthy?"
}
TASK [Notunset variable defined?] ************************************************************************************************************************
ok: [localhost] => {
"msg": "unset is defined"
}
TASK [Notunset variable defined and truthy?] *************************************************************************************************************
fatal: [localhost]: FAILED! => {
"assertion": "notunset is defined and notunset | bool",
"changed": false,
"evaluated_to": false,
"msg": "Assertion failed"
} ...ignoring
Huh. Why does this fail?
A little modification though, and it succeeds:
- name: Notunset variable defined and truthy?
ansible.builtin.assert:
that: notunset is defined and notunset
ignore_errors: true
TASK [Notunset variable defined and truthy?] *************************************************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "All assertions passed" }
When doing a little more testing, this interacts properly with assert with undefined, null, and empty strings, properly turning them into falsey and truthy values.
Another thing to note is the default filter
. When a variable is undefined, it will assign it a default value.
Interestingly, this also considers null to be undefined. When working with the test_var
, which defined but never assigned a value (null
):
- name: How about the default filter?
ansible.builtin.debug:
msg: "{{ test_var | default('test_var is not defined') }}"
TASK [How about the default filter?] *********************************************************************************************************************
ok: [localhost] => {
"msg": "" }
Null is considered defined again, as shown here.
when: variable | default(False)
Creates an elegant way to check if a variable exists, and set it to a False otherwise. This is the final solution I’ve settled on, for when I can’t guarantee variables are defined (including null
or an empty list/string)